Add coldsync.
authorrobertl <robertl@f51c46e8-681c-474f-0cfe-069cfd0219fb>
Fri, 16 Aug 2002 06:14:09 +0000 (06:14 +0000)
committerrobertl <robertl@f51c46e8-681c-474f-0cfe-069cfd0219fb>
Fri, 16 Aug 2002 06:14:09 +0000 (06:14 +0000)
gpsbabel/coldsync/Makefile [new file with mode: 0644]
gpsbabel/coldsync/README [new file with mode: 0644]
gpsbabel/coldsync/README.gpsbabel [new file with mode: 0644]
gpsbabel/coldsync/config.h [new file with mode: 0644]
gpsbabel/coldsync/palm.h [new file with mode: 0644]
gpsbabel/coldsync/pconn/util.h [new file with mode: 0644]
gpsbabel/coldsync/pdb.c [new file with mode: 0644]
gpsbabel/coldsync/pdb.h [new file with mode: 0644]
gpsbabel/coldsync/util.c [new file with mode: 0644]
gpsbabel/mingw/Makefile

diff --git a/gpsbabel/coldsync/Makefile b/gpsbabel/coldsync/Makefile
new file mode 100644 (file)
index 0000000..98a0344
--- /dev/null
@@ -0,0 +1,38 @@
+# $Id: Makefile,v 1.1 2002-08-16 06:13:10 robertl Exp $
+
+TOP =          ..
+SUBDIR =       libpdb
+
+LIBNAME =      pdb
+SHLIB_MAJOR =  0
+SHLIB_MINOR =  0
+
+LIBSRCS =      pdb.c util.c
+LIBOBJS =      ${LIBSRCS:.c=.o}
+SHLIBOBJS =    ${LIBSRCS:.c=.So}
+
+CLEAN =                ${LIBOBJS} ${SHLIBOBJS} ${LIBRARY} \
+               *.ln *.bak *~ core *.core .depend
+DISTCLEAN =
+SPOTLESS =
+
+DISTFILES =    Makefile ${LIBSRCS}
+
+OTHERTAGFILES =        ${LIBSRCS}
+
+include ${TOP}/Make.rules
+
+depend::
+       ${MKDEP} ${CPPFLAGS} ${LIBSRCS}
+
+all::  ${LIBRARY}
+
+# It might be a good idea later on to install the library, so that
+# people can write other programs that communicate with the Palm, but
+# not just yet.
+# install::
+
+# This is for Emacs's benefit:
+# Local Variables:     ***
+# fill-column: 75      ***
+# End:                 ***
diff --git a/gpsbabel/coldsync/README b/gpsbabel/coldsync/README
new file mode 100644 (file)
index 0000000..0d97b54
--- /dev/null
@@ -0,0 +1,211 @@
+       This is ColdSync, a tool for synchronizing data between Palm
+devices and Unix workstations.
+
+       Copyright (C) 1999-2001, Andrew Arensburger.
+
+       The latest version of this package is available at
+       http://www.ooblick.com/software/coldsync/
+
+       This package is distributable under the terms of the Artistic
+License. You should have received a file called "Artistic", which
+specifies the terms under which this package may be distributed and
+modified.
+       The Artistic License is taken from the Perl 5.005_03
+distribution, so some of the text is specific to Perl and does not
+apply to ColdSync. I hope to address this in a future release.
+
+       This product includes software developed by the University of
+California, Berkeley and its contributors.
+
+       This product includes software developed by the Apache Group
+for use in the Apache HTTP server project (http://www.apache.org/).
+       (Actually, the Apache code in question was written by Panos
+Tsirigotis. See comments in "src/ap_snprintf.c".)
+
+              ----------------------------------------
+
+* WHAT IS COLDSYNC?
+
+       ColdSync is a tool for synchronizing data between Palm
+computing devices (such as the PalmPilot, PalmPilot Pro, Palm V,
+QualComm PDQ, Handspring Visor and so forth), and a Unix workstation.
+       ColdSync can back up and restore the state of a Palm, as well
+as synchronize its data, which is sort of like a two-way rdist (see
+below). In future versions, it will be possible to do more interesting
+things with this data.
+
+* WHAT YOU'LL NEED
+
+       - A POSIX-compliant operating system, preferably some flavor
+         of Unix
+       - An ANSI C compiler
+       - An ANSI C++ compiler
+       - Perl 5.005_03 or later (though earlier versions might work)
+
+* BUILDING AND INSTALLING COLDSYNC
+
+       If you've built GNU software before, this should be familiar
+territory. You should be able to just
+
+       ./configure
+       make
+       make install
+
+Full details are provided in the "INSTALL" file.
+
+* WHAT IS SYNCHRONIZING?
+
+       Synchronizing, also referred to as "syncing" refers to the
+process of examining two databases (everything on the Palm is a
+database) to see how they differ, and updating them so that they are
+identical.
+       Syncing is different from just overwriting one database with
+the other. For instance, if you add an entry for "Aunt Mabel" in your
+Palm's address book, and an entry for "Uncle Bob" on your desktop
+machine, then you don't want to just copy the address book from the
+Palm to the desktop or vice-versa: that would delete one of the
+entries that you just created. When you sync with ColdSync, you'll
+wind up with both entries, on both the Palm and the desktop.
+
+       Another difference between synchronizing and blind copying
+lies in the fact that PalmOS has facilities to support syncing. If you
+have 2000 entries in your Palm address book and want to copy them to
+the desktop, it'll take a rather long time to copy them over a
+relatively slow serial connection. When it syncs, ColdSync copies only
+those records that have changed.
+
+       ColdSync tries to be very cautious when it syncs, and not
+delete any information unless it is sure that that is the right thing
+to do. Its attitude is that it's better to err on the side of caution,
+and maybe make you delete something twice, than it is to delete some
+crucial bit of information.
+
+* SECURITY CONSIDERATIONS
+
+       ColdSync is not secure. Period.
+       I have tried to pay due attention to security considerations,
+but the sync process itself is inherently insecure. When ColdSync,
+running on a workstation, receives a connection from a Palm, it has no
+reliable way of knowing that the Palm on the other end is in fact the
+one that it claims to be.
+       Likewise, when a Palm syncs with a workstation (whether that
+workstation is using ColdSync or Palm's own HotSync), it has no
+reliable way of knowing that the workstation is the one that it claims
+to be.
+       PalmOS allows you to mark records as "private." This doesn't
+mean a thing when you sync: the private and non-private records are
+treated equally. In particular, anyone who has physical access to your
+Palm can download your private records.
+
+       There may be a Palm utility out there that will encrypt each
+record in a database before a sync, but I don't know of any such
+utility.
+
+* INTERNATIONALIZATION
+
+       ColdSync includes some internationalization (i18n) support. It
+is believed to work on all platforms with a Uniforum-compliant libintl
+(gettext()). ColdSync does not work with XPG i18n (catgets() etc.).
+       However, you need GNU xgettext to compile the message catalog
+("i18n/messages.po") from the source files.
+
+              ----------------------------------------
+
+COMPATIBILITY NOTES
+
+* ColdSync 2.2.4
+
+FreeBSD:
+       This package was developed under FreeBSD 3.x/4.x, and compiles
+       cleanly with no modification under 4.2-RELEASE.
+
+       On newer versions of FreeBSD (4.0 and beyond), it's possible
+       to communicate with the Handspring Visor using its USB
+       interface. Configure a listen type of "usb" rather than
+       "serial", and use device /dev/ugen0.
+
+Redhat Linux 6.2 (also Debian, and probably others):
+       (as of ColdSync v1.6.6-20010130)
+       Compiles cleanly with no modifications. Runs fine.
+
+       The Linux serial device driver appears to drop characters at
+       random. As a result, you may see a lot of
+
+       ##### Got an unexpected data packet. Sending an ACK to shut it up.
+
+       messages.
+
+Solaris 2.7:
+       (as of v2.2.4)
+       There are still some problems with ColdSync's IPv6 code under
+       Solaris 2.6 and later. You'll need to use
+               ./configure --without-ipv6
+
+       (as of v2.2.0-20010805)
+       Compiles with Sun Forte 6U1.
+
+       ColdSync is known not to work with Sun's i18n utilities.
+       ColdSync should detect this, and disable i18n.
+
+Digital Unix 4.0:
+       (as of ColdSync v2.4.4-20011113)
+       DEC's linker chokes on the overly-long identifiers produced by
+       the STL. GNU ld might work.
+
+       (as of ColdSync v1.6.6-20010130)
+       Compiles with gcc 2.7.2. Compilation prints several warnings;
+       they appear to be benign:
+
+       PConnection_serial.c:444: warning: implicit declaration of function `cfmakeraw'
+       PConnection_net.c:281: warning: passing arg 6 of `_Erecvfrom' from incompatiblepointer type
+       PConnection_net.c:674: warning: passing arg 6 of `_Erecvfrom' from incompatiblepointer type
+       PConnection_net.c:872: warning: passing arg 3 of `_Eaccept' from incompatible pointer type
+       config.c:964: warning: overflow in implicit constant conversion
+       GenericConduit.cc:82: warning: unused parameter `const struct conduit_block * block'
+
+       If you are using DEC's C compiler, I suggest the following
+       compiler flags:
+               -std1 -msg_enable level3
+
+
+AIX 4.1:
+       (as of ColdSync v1.1.2)
+       Compiles with gcc 2.7.2, but when linking, complains that:
+
+               ld: 0711-224 WARNING: Duplicate symbol: _IO_cleanup_registration_needed
+               ld: 0711-345 Use the -bloadmap or -bnoquiet option to obtain more information.
+
+       This may be a problem with the installation, though (the same
+       thing happens when compiling "Hello, world").
+
+       I've only compiled it. I don't know whether it actually runs.
+
+If you have any updated information, please send it in to the
+maintainer (arensb@ooblick.com). My testing pool isn't as large as it
+once was.
+
+Windows NT:
+       (as of ColdSync 1.4.5)
+       To the best of my knowledge, ColdSync compiles and runs under
+       Windows NT with the Cygwin tools.
+
+       However, ColdSync was written as a Unix tool. Windows users
+       have the HotSync desktop tools from Palm, which work quite
+       well. If ColdSync works under Windows, that's wonderful, but
+       I'm not going to let Windows compatibility get in the way of
+       Unix development.
+
+MacOS X:
+       (as of ColdSync 1.4.6)
+
+       According to one correspondent, ColdSync compiles and runs
+       with no problems under MacOS X.
+       The serial port should be /dev/ttyd.printer .
+
+              ----------------------------------------
+
+BUGS:
+       If you create a Memo record, delete it without leaving the
+editor, and check the "Save archive copy on PC" box, it will be
+archived, but the archived record may contain trailing garbage.
+       This is due to a bug in PalmOS (as of 3.0).
diff --git a/gpsbabel/coldsync/README.gpsbabel b/gpsbabel/coldsync/README.gpsbabel
new file mode 100644 (file)
index 0000000..f99d837
--- /dev/null
@@ -0,0 +1,9 @@
+This directory is a subset of coldsync-2.2.5.  GPSbabel needs very 
+limited set of that functionality to read and write Palm/OS files.  
+I was faced with either reimplementing it (and I DON'T want to 
+become a Palm/OS expert) or cribbing the code.   Since it's all under 
+Artistic license, I took libpdb and the includes, whacked out the most 
+horribly non-portable pieces, jacked in a constand config.h, and pointed 
+the makefiles to it.
+
+
diff --git a/gpsbabel/coldsync/config.h b/gpsbabel/coldsync/config.h
new file mode 100644 (file)
index 0000000..5d48971
--- /dev/null
@@ -0,0 +1,8 @@
+/*
+ * Assume we're on a conformant ISO C platform.
+ */
+
+
+#define STDC_HEADERS 1
+#define _(str)        str
+// #define bzero(str, len)             memset((str), 0, (len))
diff --git a/gpsbabel/coldsync/palm.h b/gpsbabel/coldsync/palm.h
new file mode 100644 (file)
index 0000000..03a189f
--- /dev/null
@@ -0,0 +1,53 @@
+/* palm.h
+ * Definitions of various types that PalmOS likes to use.
+ *
+ *     Copyright (C) 2001, Andrew Arensburger.
+ *     You may distribute this file under the terms of the Artistic
+ *     License, as specified in the README file.
+ *
+ * $Id: palm.h,v 1.1 2002-08-16 06:13:10 robertl Exp $
+ */
+#ifndef _palm_h_
+#define _palm_h_
+
+/* Convenience types */
+typedef signed   char  byte;           /* Signed 8-bit quantity */
+typedef unsigned char  ubyte;          /* Unsigned 8-bit quantity */
+typedef signed   short word;           /* Signed 16-bit quantity */
+typedef unsigned short uword;          /* Unsigned 16-bit quantity */
+typedef signed   long  dword;          /* Signed 32-bit quantity */
+typedef unsigned long  udword;         /* Unsigned 32-bit quantity */
+
+typedef udword chunkID;                        /* Those IDs made up of four
+                                        * characters stuck together into a
+                                        * 32-bit quantity.
+                                        */
+
+/* Explicitly define the sizes of types. Can't depend on the host's types
+ * having the same size as the Palm. For instance, Alphas are 64-bit
+ * machines, so 'unsigned long' is 8 bytes, whereas 'udword' is only 4
+ * bytes.
+ */
+#define SIZEOF_BYTE    1
+#define SIZEOF_UBYTE   1
+#define SIZEOF_WORD    2
+#define SIZEOF_UWORD   2
+#define SIZEOF_DWORD   4
+#define SIZEOF_UDWORD  4
+
+/* MAKE_CHUNKID
+ * A convenience macro to make a chunkID out of four characters.
+ */
+#define MAKE_CHUNKID(a,b,c,d) \
+       (((a) << 24) | \
+        ((b) << 16) | \
+        ((c) << 8)  | \
+        (d))
+
+/* XXX - There ought to be something to make sure that the sizes and
+ * signedness above are true.
+ */
+
+typedef enum { False = 0, True = 1 } Bool;
+
+#endif /* _palm_h_ */
diff --git a/gpsbabel/coldsync/pconn/util.h b/gpsbabel/coldsync/pconn/util.h
new file mode 100644 (file)
index 0000000..88281aa
--- /dev/null
@@ -0,0 +1,61 @@
+/* util.h
+ * Misc. useful stuff.
+ *
+ *     Copyright (C) 1999-2000, Andrew Arensburger.
+ *     You may distribute this file under the terms of the Artistic
+ *     License, as specified in the README file.
+ *
+ * $Id: util.h,v 1.1 2002-08-16 06:14:09 robertl Exp $
+ */
+#ifndef _util_h_
+#define _util_h_
+
+#include <stdio.h>
+#include <time.h>
+#include "palm.h"
+// #include "dlp_cmd.h"
+
+/* XXX - The functions declared INLINE, below, really ought to be inline
+ * functions. I'm not sure how to do this portably, though.
+ */
+#ifdef __GNUC__
+#  define INLINE __inline__
+#else
+#  define INLINE
+#endif /* __GNUC__ */
+
+/* Functions for reading a value from an array of ubytes */
+extern INLINE ubyte peek_ubyte(const ubyte *buf);
+extern INLINE uword peek_uword(const ubyte *buf);
+extern INLINE udword peek_udword(const ubyte *buf);
+
+/* Functions for extracting values from an array of ubytes */
+extern INLINE ubyte get_ubyte(const ubyte **buf);
+extern INLINE uword get_uword(const ubyte **buf);
+extern INLINE udword get_udword(const ubyte **buf);
+
+/* Functions for writing values to an array of ubytes */
+extern INLINE void put_ubyte(ubyte **buf, const ubyte value);
+extern INLINE void put_uword(ubyte **buf, const uword value);
+extern INLINE void put_udword(ubyte **buf, const udword value);
+
+#if TIME
+/* Functions for converting between DLP's time format and Unix's
+ * time_ts and the time_t-with-offset that the rest of the Palm stuff
+ * uses.
+ */
+extern time_t time_dlp2time_t(const struct dlp_time *dlpt);
+extern udword time_dlp2palmtime(const struct dlp_time *dlpt);
+extern void time_time_t2dlp(const time_t t, struct dlp_time *dlpt);
+extern void time_palmtime2dlp(const udword palmt, struct dlp_time *dlpt);
+
+extern void debug_dump(FILE *outfile, const char *prefix,
+                      const ubyte *buf, const udword len);
+#endif
+#endif /* _util_h_ */
+
+\f/* This is for Emacs's benefit:
+ * Local Variables: ***
+ * fill-column:        75 ***
+ * End: ***
+ */
diff --git a/gpsbabel/coldsync/pdb.c b/gpsbabel/coldsync/pdb.c
new file mode 100644 (file)
index 0000000..d34d05e
--- /dev/null
@@ -0,0 +1,2014 @@
+/* pdb.c
+ *
+ * Functions for dealing with Palm databases and such.
+ *
+ *     Copyright (C) 1999-2001, Andrew Arensburger.
+ *     You may distribute this file under the terms of the Artistic
+ *     License, as specified in the README file.
+ *
+ * $Id: pdb.c,v 1.1 2002-08-16 06:13:10 robertl Exp $
+ */
+/* XXX - The way zero-length records are handled is a bit of a kludge. They
+ * shouldn't normally exist, with the exception of expunged records. But,
+ * of course, a malformed conduit or something can create them.
+ * The half-assed way they're handled here is to a) not upload zero-length
+ * records to the Palm, b) warn the user if they're written to a file, c)
+ * provide a utility (in the p5-Palm package) to delete zero-length
+ * records.
+ */
+/* XXX - This is a library. It shouldn't print error messages.
+ * Add 'int pdb_errno'; define error numbers and error messages that go
+ * with them.
+ * Debugging messages should go to 'FILE *pdb_logfile'.
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <fcntl.h>             /* For open() */
+#include <sys/types.h>
+// #include <sys/uio.h>
+// #include <sys/param.h>              /* For MAXPATHLEN */
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+
+#if STDC_HEADERS
+# include <string.h>           /* For strncat(), memcpy() et al. */
+#else  /* STDC_HEADERS */
+# ifndef HAVE_STRCHR
+#  define strchr index
+#  define strrchr rindex
+# endif        /* HAVE_STRCHR */
+# ifndef HAVE_MEMCPY
+#  define memcpy(d,s,n)                bcopy ((s), (d), (n))
+#  define memmove(d,s,n)       bcopy ((s), (d), (n))
+# endif        /* HAVE_MEMCPY */
+#endif /* STDC_HEADERS */
+
+/* XXX - Is this right? Should this be in the "else" clause, above? */
+#if HAVE_STRINGS_H
+#  include <strings.h>         /* For bzero() */
+#endif /* HAVE_STRINGS_H */
+
+#if HAVE_LIBINTL_H
+#  include <libintl.h>         /* For i18n */
+#endif /* HAVE_LIBINTL_H */
+
+#include <palm.h>
+#include "pdb.h"
+
+/* XXX - The functions declared INLINE, below, really ought to be inline
+ * functions. I'm not sure how to do this portably, though.
+ */
+#ifdef __GNUC__
+#  define INLINE __inline__
+#else
+#  define INLINE
+#endif /* __GNUC__ */
+
+/* Functions for extracting values from an array of ubytes */
+extern INLINE ubyte get_ubyte(const ubyte **buf);
+extern INLINE uword get_uword(const ubyte **buf);
+extern INLINE udword get_udword(const ubyte **buf);
+
+/* Functions for writing values to an array of ubytes */
+extern INLINE void put_ubyte(ubyte **buf, const ubyte value);
+extern INLINE void put_uword(ubyte **buf, const uword value);
+extern INLINE void put_udword(ubyte **buf, const udword value);
+extern void debug_dump(FILE *outfile, const char *prefix,
+                      const ubyte *buf, const udword len);
+
+int pdb_trace = 0;             /* Debugging level for PDB stuff */
+#define PDB_TRACE(n)   if (pdb_trace >= (n))
+
+/* Helper functions */
+static uword get_file_length(int fd);
+int pdb_LoadHeader(int fd, struct pdb *db);
+                       /* pdb_LoadHeader() is visible to other files */
+static int pdb_LoadRecListHeader(int fd, struct pdb *db);
+static int pdb_LoadRsrcIndex(int fd, struct pdb *db);
+static int pdb_LoadRecIndex(int fd, struct pdb *db);
+static int pdb_LoadAppBlock(int fd, struct pdb *db);
+static int pdb_LoadSortBlock(int fd, struct pdb *db);
+static int pdb_LoadResources(int fd, struct pdb *db);
+static int pdb_LoadRecords(int fd, struct pdb *db);
+
+/* merge_attributes
+ * Takes a record's flags and category, and merges them into a single byte,
+ * with the flags in the top nybble and the category in the bottom one
+ */
+static inline ubyte
+merge_attributes(const ubyte flags,
+                const ubyte category)
+{
+       /* The PDB_REC_ARCHIVED flag is troublesome, since it overlaps the
+        * category field. The idea here is that if the record was deleted,
+        * then it doesn't have a category anymore, so the category part
+        * gets set to 0.
+        */
+       if ((flags & PDB_REC_DELETED) == 0)
+               return (flags & 0xf0) |
+                       (category & 0x0f);
+       else
+               return (flags & 0xf8);
+}
+
+/* split_attributes
+ * The converse of merge_attributes(). Takes the combined field attributes
+ * and writes its contents to *flags and *category, using the same rules as
+ * merge_attributes(), above.
+ */
+static inline void
+split_attributes(const ubyte attributes,
+                ubyte *flags,
+                ubyte *category)
+{
+       if ((attributes & PDB_REC_DELETED) == 0)
+       {
+               *flags = (attributes & 0xf0);
+               *category = (attributes & 0x0f);
+       } else {
+               *flags = (attributes & 0xf8);
+               *category = 0;
+       }
+       PDB_TRACE(6)
+               fprintf(stderr, "split 0x%02x into 0x%02x, 0x%02x\n",
+                       attributes, *flags, *category);
+}
+
+/* new_pdb
+ * struct pdb constructor.
+ */
+struct pdb *
+new_pdb()
+{
+       struct pdb *retval;
+
+       /* Allocate the new pdb */
+       if ((retval = (struct pdb *) malloc(sizeof(struct pdb))) == NULL)
+               /* Out of memory */
+               return NULL;
+
+       /* Write zeros all over it, just for safety */
+       memset((void *) retval, 0, sizeof(struct pdb));
+
+       return retval;
+}
+
+/* pdb_FreeRecord
+ * Free a previously-allocated 'pdb_record'. This function wouldn't really
+ * be necessary, except that pdb_CopyRecord() returns a 'pdb_record'.
+ */
+void
+pdb_FreeRecord(struct pdb_record *rec)
+{
+       if (rec->data != NULL)
+               free(rec->data);
+       free(rec);
+}
+
+/* pdb_FreeResource
+ * Free a previously-allocated 'pdb_resource'. This function wouldn't
+ * really be necessary, except that pdb_CopyResource() returns a
+ * 'pdb_resource'.
+ */
+void
+pdb_FreeResource(struct pdb_resource *rsrc)
+{
+       if (rsrc->data != NULL)
+               free(rsrc->data);
+       free(rsrc);
+}
+
+/* free_pdb
+ * Cleanly free a struct pdb, and all of its subparts (destructor).
+ */
+void
+free_pdb(struct pdb *db)
+{
+       PDB_TRACE(7)
+               fprintf(stderr, "Inside free_pdb(%p)\n", (void *) db);
+
+       if (db == NULL)
+               /* Trivial case */
+               return;
+
+       /* Free the array of records/resources */
+       if (IS_RSRC_DB(db))
+       {
+               /* It's a resource database */
+               struct pdb_resource *rsrc;
+               struct pdb_resource *next;
+
+               PDB_TRACE(8)
+                       fprintf(stderr, "Freeing resource list\n");
+
+               /* Walk the linked list, freeing as we go along */
+               for (rsrc = db->rec_index.rsrc;
+                    rsrc != NULL;
+                    rsrc = next)
+               {
+                       next = rsrc->next;      /* Remember the next
+                                                * element on the list. We
+                                                * won't have a chance to
+                                                * look it up after this
+                                                * one has been free()d.
+                                                */
+
+                       /* Free this element */
+                       pdb_FreeResource(rsrc);
+               }
+       } else {
+               /* It's a record database */
+               struct pdb_record *rec;
+               struct pdb_record *next;
+
+               PDB_TRACE(8)
+                       fprintf(stderr, "Freeing record list\n");
+
+               /* Walk the linked list, freeing as we go along */
+               for (rec = db->rec_index.rec;
+                    rec != NULL;
+                    rec = next)
+               {
+                       next = rec->next;       /* Remember the next
+                                                * element on the list. We
+                                                * won't have a chance to
+                                                * look it up after this
+                                                * one has been free()d.
+                                                */
+
+                       /* Free this element */
+                       pdb_FreeRecord(rec);
+               }
+       }
+
+       /* Free the sort block */
+       if (db->sortinfo != NULL)
+               free(db->sortinfo);
+
+       /* Free the app info block */
+       if (db->appinfo != NULL)
+               free(db->appinfo);
+
+       free(db);
+}
+
+/* pdb_Read
+ * Read a PDB from the file descriptor 'fd'. This must already have been
+ * opened for reading and/or writing.
+ *
+ * Note: this function does not to any locking. The caller is responsible
+ * for that.
+ */
+struct pdb *
+pdb_Read(int fd)
+{
+       int err;
+       struct pdb *retval;
+
+       /* Create a new pdb to return */
+       if ((retval = new_pdb()) == NULL)
+       {
+               return NULL;
+       }
+
+       /* Find out how long the file is */
+       retval->file_size = get_file_length(fd);
+       if (retval->file_size == ~0)
+       {
+               /* The file isn't seekable */
+               fprintf(stderr, _("File isn't seekable.\n"));
+               free_pdb(retval);
+               return NULL;
+       }
+
+       /* Load the header */
+       if ((err = pdb_LoadHeader(fd, retval)) < 0)
+       {
+               fprintf(stderr, _("Can't load header.\n"));
+               free_pdb(retval);
+               return NULL;
+       }
+
+       /* Load the record list header */
+       if ((err = pdb_LoadRecListHeader(fd, retval)) < 0)
+       {
+               fprintf(stderr, _("Can't load record list header for "
+                                 "\"%.*s\".\n"),
+                       PDB_DBNAMELEN, retval->name);
+               free_pdb(retval);
+               return NULL;
+       }
+
+       /* Read the record/resource list */
+       if (IS_RSRC_DB(retval))
+       {
+               /* Read the resource index */
+               if ((err = pdb_LoadRsrcIndex(fd, retval)) < 0)
+               {
+                       fprintf(stderr, _("Can't read resource index for "
+                                         "\"%.*s\".\n"),
+                               PDB_DBNAMELEN, retval->name);
+                       free_pdb(retval);
+                       return NULL;
+               }
+       } else {
+               /* Read the record index */
+               if ((err = pdb_LoadRecIndex(fd, retval)) < 0)
+               {
+                       fprintf(stderr, _("Can't read record index for "
+                                         "\"%.*s\".\n"),
+                               PDB_DBNAMELEN, retval->name);
+                       free_pdb(retval);
+                       return NULL;
+               }
+       }
+
+       /* In most PDBs, there are two NUL bytes here. They are allowed by
+        * the spec, but not mandated, and some PDBs don't have them. We'll
+        * ignore them for now, and have the appropriate pdb_Load*()
+        * function lseek() to the proper position.
+        */
+
+       /* Load the AppInfo block, if any */
+       if ((err = pdb_LoadAppBlock(fd, retval)) < 0)
+       {
+               fprintf(stderr, _("Can't read AppInfo block for "
+                                 "\"%.*s\".\n"),
+                       PDB_DBNAMELEN, retval->name);
+               free_pdb(retval);
+               return NULL;
+       }
+
+       /* Load the sort block, if any */
+       if ((err = pdb_LoadSortBlock(fd, retval)) < 0)
+       {
+               fprintf(stderr, _("Can't read sort block for "
+                                 "\"%.*s\".\n"),
+                       PDB_DBNAMELEN, retval->name);
+               free_pdb(retval);
+               return NULL;
+       }
+
+       /* Load the records themselves */
+       if (IS_RSRC_DB(retval))
+       {
+               /* Read the resources */
+               if ((err = pdb_LoadResources(fd, retval)) < 0)
+               {
+                       fprintf(stderr, _("Can't read resources for "
+                                         "\"%.*s\".\n"),
+                               PDB_DBNAMELEN, retval->name);
+                       free_pdb(retval);
+                       return NULL;
+               }
+       } else {
+               /* Read the records */
+               if ((err = pdb_LoadRecords(fd, retval)) < 0)
+               {
+                       fprintf(stderr, _("Can't read records for "
+                                         "\"%.*s\".\n"),
+                               PDB_DBNAMELEN, retval->name);
+                       free_pdb(retval);
+                       return NULL;
+               }
+       }
+
+       return retval;                  /* Success */
+}
+
+/* pdb_Write
+ * Write 'db' to the file descriptor 'fd'. This must already have been
+ * opened for writing.
+ *
+ * Note that while you can open the backup file for reading and writing,
+ * read from it with pdb_Read() and save it with pdb_Write(), this is not
+ * recommended: if anything should go wrong at the wrong time (e.g., the
+ * disk fills up just as you're about to write the database back to disk),
+ * you will lose the entire backup.
+ * A better approach is to use a staging file: read from the backup file,
+ * write to a temporary file, then use rename() to move the temporary file
+ * onto the real one. Alternately, you can copy the original file to a
+ * temporary one, then open the temporary for both reading and writing.
+ * This might have some advantages, in that it allows you to lock a single
+ * file for the duration of the sync.
+ *
+ * Note: this function does not lock the file. The caller is responsible
+ * for that.
+ */
+int
+pdb_Write(const struct pdb *db,
+         int fd)
+{
+       static ubyte header_buf[PDB_HEADER_LEN];
+                               /* Buffer for writing database header */
+       static ubyte rlheader_buf[PDB_RECORDLIST_LEN];
+                               /* Buffer for writing the record list header */
+       static ubyte nul_buf[2];
+                               /* Buffer for writing the two useless NULs */
+       ubyte *wptr;            /* Pointer into buffers, for writing */
+       udword offset;          /* The next offset we're interested in */
+
+       /* Initialize 'offset': the next variable-sized item will go after
+        * the header, after the index header, after the index, after the
+        * two useless NULs.
+        */
+       offset = PDB_HEADER_LEN + PDB_RECORDLIST_LEN;
+       if (IS_RSRC_DB(db))
+               offset += db->numrecs * PDB_RESOURCEIX_LEN;
+       else
+               offset += db->numrecs * PDB_RECORDIX_LEN;
+       offset += 2;            /* Those two useless NUL bytes */
+
+       /** Write the database header **/
+
+       /* Construct the header in 'header_buf' */
+       wptr = header_buf;
+       memcpy(wptr, db->name, PDB_DBNAMELEN);
+       wptr += PDB_DBNAMELEN;
+       put_uword(&wptr, (db->attributes & ~PDB_ATTR_OPEN));
+                               /* Clear the 'open' flag before writing */
+       put_uword(&wptr, db->version);
+       put_udword(&wptr, db->ctime);
+       put_udword(&wptr, db->mtime);
+       put_udword(&wptr, db->baktime);
+       put_udword(&wptr, db->modnum);
+       if (db->appinfo == NULL)        /* Write the AppInfo block, if any */
+               /* This database doesn't have an AppInfo block */
+               put_udword(&wptr, 0L);
+       else {
+               /* This database has an AppInfo block */
+               put_udword(&wptr, offset);
+               offset += db->appinfo_len;
+       }
+       if (db->sortinfo == NULL)       /* Write the sort block, if any */
+               /* This database doesn't have a sort block */
+               put_udword(&wptr, 0L);
+       else {
+               put_udword(&wptr, offset);
+               offset += db->sortinfo_len;
+       }
+       put_udword(&wptr, db->type);
+       put_udword(&wptr, db->creator);
+       put_udword(&wptr, db->uniqueIDseed);
+
+       /* Write the database header */
+       if (write(fd, header_buf, PDB_HEADER_LEN) != PDB_HEADER_LEN)
+       {
+               fprintf(stderr, _("%s: can't write database header for "
+                                 "\"%.*s\".\n"),
+                       "pdb_Write",
+                       PDB_DBNAMELEN, db->name);
+               perror("write");
+               close(fd);
+               return -1;
+       }
+
+       /** Write the record/resource index header **/
+       /* Construct the record list header */
+       wptr = rlheader_buf;
+       put_udword(&wptr, 0L);  /* nextID */
+                       /* XXX - What is this? Should this be something
+                        * other than 0? */
+       put_uword(&wptr, db->numrecs);
+
+       /* Write the record list header */
+       if (write(fd, rlheader_buf, PDB_RECORDLIST_LEN) != PDB_RECORDLIST_LEN)
+       {
+               fprintf(stderr, _("%s: can't write record list header for "
+                                 "\"%.*s\".\n"),
+                       "pdb_Write",
+                       PDB_DBNAMELEN, db->name);
+               perror("write");
+               return -1;
+       }
+
+       /* Write the record/resource index */
+       if (IS_RSRC_DB(db))
+       {
+               /* It's a resource database */
+               struct pdb_resource *rsrc;      /* Current resource */
+
+               /* Go through the list of resources, writing each one */
+               for (rsrc = db->rec_index.rsrc;
+                    rsrc != NULL;
+                    rsrc = rsrc->next)
+               {
+                       static ubyte rsrcbuf[PDB_RESOURCEIX_LEN];
+                                       /* Buffer to hold the resource
+                                        * index entry.
+                                        */
+
+                       /* Construct the resource index entry */
+                       wptr = rsrcbuf;
+                       put_udword(&wptr, rsrc->type);
+                       put_uword(&wptr, rsrc->id);
+                       put_udword(&wptr, offset);
+
+                       /* Write the resource index entry */
+                       if (write(fd, rsrcbuf, PDB_RESOURCEIX_LEN) !=
+                           PDB_RESOURCEIX_LEN)
+                       {
+                               fprintf(stderr, _("%s: Can't write resource "
+                                                 "index entry for "
+                                                 "\"%.*s\".\n"),
+                                       "pdb_Write",
+                                       PDB_DBNAMELEN, db->name);
+                               perror("write");
+                               return -1;
+                       }
+
+                       /* Bump 'offset' up to point to the offset of the
+                        * next variable-sized thing in the file.
+                        */
+                       offset += rsrc->data_len;
+               }
+       } else {
+               /* It's a record database */
+               struct pdb_record *rec;         /* Current record */
+
+               /* Go through the list of records, writing each one */
+               for (rec = db->rec_index.rec; rec != NULL; rec = rec->next)
+               {
+                       static ubyte recbuf[PDB_RECORDIX_LEN];
+                                       /* Buffer to hold the record index
+                                        * entry.
+                                        */
+
+                       /* Construct the record index entry */
+                       wptr = recbuf;
+
+                       /* Sanity check */
+                       if (rec->data_len == 0)
+                       {
+                               fprintf(stderr,
+                                       _("\"%.*s\" record 0x%08lx has "
+                                         "length 0.\n"),
+                                       PDB_DBNAMELEN, db->name,
+                                       rec->id);
+                       }
+
+                       put_udword(&wptr, offset);
+                       put_ubyte(&wptr, merge_attributes(
+                               rec->flags,
+                               rec->category));
+                       put_ubyte(&wptr, (char) ((rec->id >> 16) & 0xff));
+                       put_ubyte(&wptr, (char) ((rec->id >> 8) & 0xff));
+                       put_ubyte(&wptr, (char) (rec->id & 0xff));
+
+                       /* Write the resource index entry */
+                       if (write(fd, recbuf, PDB_RECORDIX_LEN) !=
+                           PDB_RECORDIX_LEN)
+                       {
+                               fprintf(stderr, _("%s: Can't write record "
+                                                 "index entry for "
+                                                 "\"%.*s\".\n"),
+                                       "pdb_Write",
+                                       PDB_DBNAMELEN, db->name);
+                               perror("write");
+                               return -1;
+                       }
+
+                       /* Bump 'offset' up to point to the offset of the
+                        * next variable-sized thing in the file.
+                        */
+                       offset += rec->data_len;
+               }
+       }
+
+       /* Write the two useless NUL bytes */
+       nul_buf[0] = nul_buf[1] = '\0';
+       if (write(fd, nul_buf, 2) != 2)
+       {
+               fprintf(stderr, _("%s: Can't write the two useless NULs to "
+                                 "\"%.*s\".\n"),
+                       "pdb_Write",
+                       PDB_DBNAMELEN, db->name);
+               perror("write");
+               return -1;
+       }
+
+       /* Write the AppInfo block, if any */
+       if (db->appinfo != NULL)
+       {
+               if (write(fd, db->appinfo, db->appinfo_len) !=
+                   db->appinfo_len)
+               {
+                       fprintf(stderr, _("%s: Can't write AppInfo block for "
+                                         "\"%.*s\".\n"),
+                               "pdb_Write",
+                               PDB_DBNAMELEN, db->name);
+                       perror("write");
+                       return -1;
+               }
+       }
+
+       /* Write the sort block, if any */
+       if (db->sortinfo != NULL)
+       {
+               if (write(fd, db->sortinfo, db->sortinfo_len) !=
+                   db->sortinfo_len)
+               {
+                       fprintf(stderr, _("%s: Can't write sort block for "
+                                         "\"%.*s\".\n"),
+                               "pdb_Write",
+                               PDB_DBNAMELEN, db->name);
+                       perror("write");
+                       return -1;
+               }
+       }
+
+       /* Write the record/resource data */
+       if (IS_RSRC_DB(db))
+       {
+               /* It's a resource database */
+               struct pdb_resource *rsrc;
+
+               /* Go through the list of resources, writing each one's
+                * data.
+                */
+               for (rsrc = db->rec_index.rsrc;
+                    rsrc != NULL;
+                    rsrc = rsrc->next)
+               {
+                       /* Write the data */
+                       if (write(fd, rsrc->data, rsrc->data_len) !=
+                           rsrc->data_len)
+                       {
+                               fprintf(stderr, _("%s: Can't write resource "
+                                                 "data for \"%.*s\".\n"),
+                                       "pdb_Write",
+                                       PDB_DBNAMELEN, db->name);
+                               perror("write");
+                               return -1;
+                       }
+               }
+       } else {
+               /* It's a record database */
+               struct pdb_record *rec;
+
+               /* Go through the list of records, writing each one's data. */
+               for (rec = db->rec_index.rec; rec != NULL; rec = rec->next)
+               {
+                       /* Write the data */
+                       if (write(fd, rec->data, rec->data_len) !=
+                           rec->data_len)
+                       {
+                               fprintf(stderr,
+                                       _("%s: Can't write record data for "
+                                         "\"%.*s\".\n"),
+                                       "pdb_Write",
+                                       PDB_DBNAMELEN, db->name);
+                               perror("write");
+                               return -1;
+                       }
+               }
+       }
+
+       return 0;               /* Success */
+}
+
+/* pdb_FindRecordByID
+ * Find the record in 'db' whose ID is 'id'. Return a pointer to it. If no
+ * such record exists, or in case of error, returns NULL.
+ */
+struct pdb_record *
+pdb_FindRecordByID(
+       const struct pdb *db,
+       const udword id)
+{
+       struct pdb_record *rec;
+
+       /* Walk the list of records, comparing IDs */
+       for (rec = db->rec_index.rec; rec != NULL; rec = rec->next)
+       {
+               if (rec->id == id)
+                       return rec;
+       }
+
+       return NULL;            /* Couldn't find it */
+}
+
+/* pdb_FindRecordByIndex
+ * Find the 'index'th record in 'db', and return a pointer to it. If no
+ * such record exists, or in case of error, return NULL.
+ */
+struct pdb_record *
+pdb_FindRecordByIndex(
+       const struct pdb *db,   /* Database to look in */
+       const uword index)      /* Index of the record to look for */
+{
+       struct pdb_record *rec;
+       int i;
+
+       /* Walk the list, decrementing the count as we go along. If it
+        * reaches 0, we've found the record.
+        */
+       rec = db->rec_index.rec;
+       for (i = index; i > 0; i--)
+       {
+               if (rec == NULL)
+                       /* Oops! We've fallen off the end of the list */
+                       return NULL;
+               rec = rec->next;
+       }
+
+       return rec;             /* Success */
+}
+
+/* pdb_NextRecord
+ * Find the next record after 'rec' in 'db', and return a pointer to it. If
+ * 'rec' is the last record in the list, return NULL.
+ */
+struct pdb_record *
+pdb_NextRecord(const struct pdb *db,   /* Database to look in */
+              const struct pdb_record *rec)
+                                       /* Return 'rec's successor */
+{
+       return rec->next;
+}
+
+/* pdb_DeleteRecordByID
+ * Find the record whose unique ID is 'id' and delete it from 'db'. If the
+ * record isn't found, well, that's okay; we wanted to delete it anyway.
+ * Returns 0 if successful, -1 in case of error.
+ */
+int
+pdb_DeleteRecordByID(
+       struct pdb *db,
+       const udword id)
+{
+       struct pdb_record *rec;         /* Record we're looking at */
+       struct pdb_record *last;        /* Last record we saw */
+
+       if (IS_RSRC_DB(db))
+               /* This only works with record databases */
+               return -1;
+
+       /* Look through the list of records */
+       last = NULL;            /* Haven't seen any records yet */
+       for (rec = db->rec_index.rec; rec != NULL; rec = rec->next)
+       {
+               /* See if the ID matches */
+               if (rec->id == id)
+               {
+                       /* Found it */
+
+                       /* XXX - Presumably better to use pdb_FreeRecord() */
+                       /* Free 'rec's data */
+                       if (rec->data != NULL)
+                               free(rec->data);
+
+                       /* Cut 'rec' out of the list. The first element of
+                        * the list is a special case.
+                        */
+                       if (last == NULL)
+                               db->rec_index.rec = rec->next;
+                       else
+                               last->next = rec->next;
+
+                       free(rec);              /* Free it */
+                       db->numrecs--;          /* Decrement record count */
+                       
+                       return 0;       /* Success */
+               }
+
+               last = rec;     /* Remember what we just saw */
+       }
+
+       /* Couldn't find it. Oh, well. Call it a success anyway. */
+       return 0;
+}
+
+/* pdb_AppendRecord
+ * Append a new record to 'db's record list. 'newrec' is not copied, so it
+ * is important that the caller not free it afterwards.
+ */
+/* XXX - Ought to make sure that the ID is unique */
+int
+pdb_AppendRecord(struct pdb *db,
+                struct pdb_record *newrec)
+{
+       struct pdb_record *rec;
+
+       /* Sanity check */
+       if (IS_RSRC_DB(db))
+               /* This only works with record databases */
+               return -1;
+
+       /* Check to see if the list is empty */
+       if (db->rec_index.rec == NULL)
+       {
+               db->rec_index.rec = newrec;
+               newrec->next = NULL;
+
+               db->numrecs++;          /* Bump record counter */
+
+               return 0;               /* Success */
+       }
+
+       /* Walk the list to find its end */
+       for (rec = db->rec_index.rec; rec->next != NULL; rec = rec->next)
+               ;
+       rec->next = newrec;
+       newrec->next = NULL;
+
+       db->numrecs++;                  /* Bump record counter */
+
+       return 0;                       /* Success */
+}
+
+/* pdb_AppendResource
+ * Append a new resource to 'db's resource list. 'newrsrc' is not copied,
+ * so it is important that the caller not free it afterwards.
+ */
+int
+pdb_AppendResource(struct pdb *db,
+                  struct pdb_resource *newrsrc)
+{
+       struct pdb_resource *rsrc;
+
+       /* Sanity check */
+       if (!IS_RSRC_DB(db))
+               /* This only works with resource databases */
+               return -1;
+
+       /* Check to see if the list is empty */
+       if (db->rec_index.rsrc == NULL)
+       {
+               db->rec_index.rsrc = newrsrc;
+               newrsrc->next = NULL;
+
+               db->numrecs++;          /* Bump resource counter */
+
+               return 0;               /* Success */
+       }
+
+       /* Walk the list to find its end */
+       for (rsrc = db->rec_index.rsrc; rsrc->next != NULL; rsrc = rsrc->next)
+               ;
+       rsrc->next = newrsrc;
+       newrsrc->next = NULL;
+
+       db->numrecs++;                  /* Bump resource counter */
+
+       return 0;                       /* Success */
+}
+
+/* pdb_InsertRecord
+ * Insert 'newrec' into 'db', just after 'prev'. If 'prev' is NULL,
+ * 'newrec' is inserted at the beginning of the list.
+ * Returns 0 if successful, -1 otherwise.
+ * 'newrec' is not copied, so it is important that the caller not free it.
+ */
+int
+pdb_InsertRecord(struct pdb *db,       /* The database to insert into */
+                struct pdb_record *prev,
+                                       /* Insert after this record */
+                struct pdb_record *newrec)
+                                       /* The record to insert */
+{
+       /* If 'prev' is NULL, insert at the beginning of the list */
+       if (prev == NULL)
+       {
+               newrec->next = db->rec_index.rec;
+               db->rec_index.rec = newrec;
+               db->numrecs++;          /* Increment record count */
+
+               return 0;               /* Success */
+       }
+
+       /* XXX - This function doesn't actually check to make sure that
+        * 'prev' is in 'db'. You could really fuck yourself over with
+        * this.
+        * So make it a documented requirement.
+        */
+       /* The new record goes in the middle of the list. Insert it. */
+       newrec->next = prev->next;
+       prev->next = newrec;
+       db->numrecs++;                  /* Increment record count */
+
+       return 0;                       /* Success */
+}
+
+/* pdb_InsertResource
+ * Insert 'newrsrc' into 'db', just after 'prev'. If 'prev' is NULL, 'newrsrc'
+ * is inserted at the beginning of the list.
+ * Returns 0 if successful, -1 otherwise.
+ * 'newrec' is not copied, so it is important that the caller not free it.
+ */
+int
+pdb_InsertResource(struct pdb *db,     /* The database to insert into */
+                  struct pdb_resource *prev,
+                                       /* Insert after this resource */
+                  struct pdb_resource *newrsrc)
+                                       /* The resource to insert */
+{
+       /* If 'prev' is NULL, insert at the beginning of the list */
+       if (prev == NULL)
+       {
+               newrsrc->next = db->rec_index.rsrc;
+               db->rec_index.rsrc = newrsrc;
+               db->numrecs++;          /* Increment record count */
+
+               return 0;               /* Success */
+       }
+
+       /* XXX - This function doesn't actually check to make sure that
+        * 'prev' is in 'db'. You could really fuck yourself over with
+        * this.
+        * So make it a documented requirement.
+        */
+       /* The new resource goes in the middle of the list. Insert it. */
+       newrsrc->next = prev->next;
+       prev->next = newrsrc;
+       db->numrecs++;                  /* Increment record count */
+
+       return 0;                       /* Success */
+}
+
+/* new_Record
+ * Create a new record from the given arguments, and return a pointer to
+ * it. Returns NULL in case of error.
+ * The record data is copied, so the caller needs to take care of freeing
+ * 'data'.
+ *
+ * The 'attributes' and 'category' arguments are combined into one field:
+ * if the new record is deleted, then the category is silently dropped.
+ * Otherwise, the category occupies the bottom 4 bits of the
+ * attributes/category field.
+ */
+struct pdb_record *
+new_Record(const ubyte flags,
+          const ubyte category,
+          const udword id,
+          const uword len,
+          const ubyte *data)
+{
+       struct pdb_record *retval;
+
+       PDB_TRACE(6)
+       {
+               fprintf(stderr, "new_Record: Creating new record:\n");
+               fprintf(stderr, "\tflags == 0x%02x\n", flags);
+               fprintf(stderr, "\tcategory == 0x%02x\n", category);
+               fprintf(stderr, "\tid == 0x%08lx\n", id);
+               fprintf(stderr, "\tlen == %d\n", len);
+               debug_dump(stderr, "NEW", data, len);
+       }
+
+       /* Allocate the record to be returned */
+       if ((retval = (struct pdb_record *) malloc(sizeof(struct pdb_record)))
+           == NULL)
+       {
+               fprintf(stderr, _("%s: Out of memory.\n"),
+                       "new_Record");
+               return NULL;
+       }
+
+       /* Initialize the new record */
+       retval->next = NULL;
+       retval->offset = 0L;
+       retval->flags = flags;
+       retval->category = category;
+       retval->id = id;
+
+       /* Allocate space to put the record data */
+       if (len == 0)
+       {
+               /* Special case: the record has no data (e.g., this is an
+                * expunged record).
+                */
+               retval->data_len = len;
+               retval->data = NULL;
+               return retval;
+       }
+
+       if ((retval->data = (ubyte *) malloc(len)) == NULL)
+       {
+               /* Couldn't allocate data portion of record */
+               fprintf(stderr, _("%s: can't allocate data.\n"),
+                       "new_Record");
+               free(retval);
+               return NULL;
+       }
+
+       /* Copy the data to the new record */
+       retval->data_len = len;
+       memcpy(retval->data, data, len);
+
+       return retval;          /* Success */
+}
+
+/* new_Resource
+ * Create a new resource from the given arguments, and return a pointer to
+ * it. Returns NULL in case of error.
+ * The resource data is copied, so the caller needs to take care of freeing
+ * 'data'.
+ */
+struct pdb_resource *
+new_Resource(const udword type,
+            const uword id,
+            const uword len,
+            const ubyte *data)
+{
+       struct pdb_resource *retval;
+
+       PDB_TRACE(6)
+       {
+               fprintf(stderr, "new_Resource: Creating new resource:\n");
+               fprintf(stderr, "\ttype == 0x%08lx (%c%c%c%c)\n",
+                       type,
+                       (int) ((type >> 24) & 0xff),
+                       (int) ((type >> 16) & 0xff),
+                       (int) ((type >>  8) & 0xff),
+                       (int)  (type        & 0xff));
+               fprintf(stderr, "\tid == 0x%04x\n", id);
+               fprintf(stderr, "\tlen == %d\n", len);
+               debug_dump(stderr, "NEW", data, len);
+       }
+
+       /* Allocate the resource to be returned */
+       if ((retval = (struct pdb_resource *)
+            malloc(sizeof(struct pdb_resource))) == NULL)
+       {
+               fprintf(stderr, _("%s: Out of memory.\n"),
+                       "new_Resource");
+               return NULL;
+       }
+
+       /* Initialize the new resource */
+       retval->next = NULL;
+       retval->offset = 0L;
+       retval->type = type;
+       retval->id = id;
+
+       /* Allocate space to put the resource data */
+       if (len == 0)
+       {
+               /* Special case: zero-length resource (dunno if this should
+                * ever happen, but this way we avoid malloc(0).
+                */
+               retval->data_len = len;
+               retval->data = NULL;
+               return retval;
+       }
+
+       if ((retval->data = (ubyte *) malloc(len)) == NULL)
+       {
+               /* Couldn't allocate data portion of resource */
+               fprintf(stderr, _("%s: can't allocate data.\n"),
+                       "new_Resource");
+               free(retval);
+               return NULL;
+       }
+
+       /* Copy the data to the new resource */
+       retval->data_len = len;
+       memcpy(retval->data, data, len);
+
+       return retval;          /* Success */
+}
+
+/* pdb_CopyRecord
+ * Make a copy of record 'rec' in database 'db' (and its data), and return
+ * it. The new record is allocated by pdb_CopyRecord(), so the caller has
+ * to take care of freeing it.
+ * Returns a pointer to the new copy, or NULL in case of error.
+ */
+struct pdb_record *pdb_CopyRecord(
+       const struct pdb *db,
+       const struct pdb_record *rec)
+{
+       struct pdb_record *retval;
+
+       /* Allocate the record to be returned */
+       if ((retval = (struct pdb_record *) malloc(sizeof(struct pdb_record)))
+           == NULL)
+       {
+               fprintf(stderr, _("%s: Out of memory.\n"),
+                       "pdb_CopyRecord");
+               return NULL;
+       }
+
+       retval->next = NULL;            /* For cleanliness */
+
+       /* Copy the old record to the new copy */
+       retval->offset  = rec->offset;
+       retval->flags   = rec->flags;
+       retval->category = rec->category;
+       retval->id      = rec->id;
+
+       /* Allocate space for the record data itself */
+       if ((retval->data = (ubyte *) malloc(rec->data_len)) == NULL)
+       {
+               fprintf(stderr, _("%s: can't allocate record data for "
+                                 "\"%.*s\".\n"),
+                       "pdb_CopyRecord",
+                       PDB_DBNAMELEN, db->name);
+               free(retval);
+               return NULL;
+       }
+
+       /* Copy the record data */
+       retval->data_len = rec->data_len;
+       memcpy(retval->data, rec->data, retval->data_len);
+
+       return retval;          /* Success */
+}
+
+/* pdb_CopyResource
+ * Make a copy of resource 'rsrc' in database 'db' (and its data), and
+ * return it. The new record is allocated by pdb_CopyResource(), so the
+ * caller has to take care of freeing it.
+ * Returns a pointer to the new copy, or NULL in case of error.
+ */
+struct pdb_resource *pdb_CopyResource(
+       const struct pdb *db,
+       const struct pdb_resource *rsrc)
+{
+       struct pdb_resource *retval;
+
+       /* Allocate the resource to be returned */
+       if ((retval = (struct pdb_resource *)
+            malloc(sizeof(struct pdb_resource))) == NULL)
+       {
+               fprintf(stderr, _("%s: Out of memory.\n"),
+                       "pdb_CopyResource");
+               return NULL;
+       }
+
+       retval->next = NULL;            /* For cleanliness */
+
+       /* Copy the old resource to the new copy */
+       retval->type = rsrc->type;
+       retval->id = rsrc->id;
+       retval->offset = rsrc->offset;
+
+       /* Allocate space for the record data itself */
+       if ((retval->data = (ubyte *) malloc(rsrc->data_len)) == NULL)
+       {
+               fprintf(stderr, _("%s: can't allocate resource data for "
+                                 "\"%.*s\".\n"),
+                       "pdb_CopyResource",
+                       PDB_DBNAMELEN, db->name);
+               free(retval);
+               return NULL;
+       }
+
+       /* Copy the resource data */
+       retval->data_len = rsrc->data_len;
+       memcpy(retval->data, rsrc->data, retval->data_len);
+
+       return retval;          /* Success */
+}
+
+/*** Helper functions ***/
+
+/* get_file_length
+ * Return the length of a file, in bytes. In case of error, returns ~0.
+ */
+static uword
+get_file_length(int fd)
+{
+       off_t here;
+       off_t eof;
+
+       /* Get the current position within the file */
+       here = lseek(fd, 0L, SEEK_CUR);
+       if (here < 0)
+               /* The file isn't seekable, presumably either because it
+                * isn't open, or because it's a pipe/socket/FIFO/tty.
+                */
+               return ~0;
+
+       /* Go to the end of the file */
+       eof = lseek(fd, 0L, SEEK_END);
+
+       /* And return to where we were before */
+       lseek(fd, here, SEEK_SET);
+
+       return eof - here;
+}
+
+/* pdb_LoadHeader
+ * Read the header of a pdb file, and fill in the appropriate fields in
+ * 'db'.
+ */
+int
+pdb_LoadHeader(int fd,
+              struct pdb *db)
+{
+       int err;
+       static ubyte buf[PDB_HEADER_LEN];
+                               /* Buffer to hold the file header */
+       const ubyte *rptr;      /* Pointer into buffers, for reading */
+
+       /* Read the header */
+       if ((err = read(fd, buf, PDB_HEADER_LEN)) != PDB_HEADER_LEN)
+       {
+               perror("pdb_LoadHeader: read");
+               return -1;
+       }
+
+       /* Parse the database header */
+       rptr = buf;
+       memcpy(db->name, buf, PDB_DBNAMELEN);
+       rptr += PDB_DBNAMELEN;
+       db->attributes = get_uword(&rptr);
+       db->version = get_uword(&rptr);
+       db->ctime = get_udword(&rptr);
+       db->mtime = get_udword(&rptr);
+       db->baktime = get_udword(&rptr);
+       db->modnum = get_udword(&rptr);
+       db->appinfo_offset = get_udword(&rptr);
+       db->sortinfo_offset = get_udword(&rptr);
+       db->type = get_udword(&rptr);
+       db->creator = get_udword(&rptr);
+       db->uniqueIDseed = get_udword(&rptr);
+
+       PDB_TRACE(5)
+       {
+               time_t t;
+
+               fprintf(stderr, "\tname: \"%s\"\n", db->name);
+               fprintf(stderr, "\tattributes: 0x%04x", db->attributes);
+               if (db->attributes & PDB_ATTR_RESDB)
+                       fprintf(stderr, " RESDB");
+               if (db->attributes & PDB_ATTR_RO) fprintf(stderr, " RO");
+               if (db->attributes & PDB_ATTR_APPINFODIRTY)
+                       fprintf(stderr, " APPINFODIRTY");
+               if (db->attributes & PDB_ATTR_BACKUP)
+                       fprintf(stderr, " BACKUP");
+               if (db->attributes & PDB_ATTR_OKNEWER)
+                       fprintf(stderr, " OKNEWER");
+               if (db->attributes & PDB_ATTR_RESET) fprintf(stderr, " RESET");
+               if (db->attributes & PDB_ATTR_NOCOPY)
+                       fprintf(stderr, " NOCOPY");
+               if (db->attributes & PDB_ATTR_STREAM)
+                       fprintf(stderr, " STREAM");
+               if (db->attributes & PDB_ATTR_OPEN)
+                       fprintf(stderr, " OPEN");
+               fprintf(stderr, "\n");
+               fprintf(stderr, "\tversion: %u\n", db->version);
+               t = db->ctime - EPOCH_1904;
+               fprintf(stderr, "\tctime: %lu %s", db->ctime,
+                       ctime(&t));
+               t = db->mtime - EPOCH_1904;
+               fprintf(stderr, "\tmtime: %lu %s", db->mtime,
+                       ctime(&t));
+               t = db->baktime - EPOCH_1904;
+               fprintf(stderr, "\tbaktime: %lu %s", db->baktime,
+                       ctime(&t));
+               fprintf(stderr, "\tmodnum: %ld\n", db->modnum);
+               fprintf(stderr, "\tappinfo_offset: 0x%08lx\n",
+                       db->appinfo_offset);
+               fprintf(stderr, "\tsortinfo_offset: 0x%08lx\n",
+                       db->sortinfo_offset);
+               fprintf(stderr, "\ttype: '%c%c%c%c' (0x%08lx)\n",
+                       (char) (db->type >> 24) & 0xff,
+                       (char) (db->type >> 16) & 0xff,
+                       (char) (db->type >> 8) & 0xff,
+                       (char) db->type & 0xff,
+                       db->type);
+               fprintf(stderr, "\tcreator: '%c%c%c%c' (0x%08lx)\n",
+                       (char) (db->creator >> 24) & 0xff,
+                       (char) (db->creator >> 16) & 0xff,
+                       (char) (db->creator >> 8) & 0xff,
+                       (char) db->creator & 0xff,
+                       db->creator);
+               fprintf(stderr, "\tuniqueIDseed: %ld\n", db->uniqueIDseed);
+       }
+
+       return 0;               /* Success */
+}
+
+/* pdb_LoadRecListHeader
+ * Load the record list header from a pdb file, and fill in the appropriate
+ * fields in 'db'.
+ */
+static int
+pdb_LoadRecListHeader(int fd,
+                     struct pdb *db)
+{
+       int err;
+       static ubyte buf[PDB_RECORDLIST_LEN];
+       const ubyte *rptr;      /* Pointer into buffers, for reading */
+
+       /* Read the record list header */
+       if ((err = read(fd, buf, PDB_RECORDLIST_LEN)) != PDB_RECORDLIST_LEN)
+       {
+               perror("pdb_LoadRecListHeader: read2");
+               return -1;
+       }
+
+       /* Parse the record list */
+       rptr = buf;
+       db->next_reclistID = get_udword(&rptr);
+       db->numrecs = get_uword(&rptr);
+
+       PDB_TRACE(6)
+       {
+               fprintf(stderr, "\tnextID: %ld\n", db->next_reclistID);
+               fprintf(stderr, "\tlen: %u\n", db->numrecs);
+       }
+
+       return 0;
+}
+
+/* pdb_LoadRsrcIndex
+ * Read the resource index from a resource database file, and fill in the
+ * appropriate fields in 'db'.
+ */
+static int
+pdb_LoadRsrcIndex(int fd,
+                 struct pdb *db)
+{
+       int i;
+       int err;
+       uword totalrsrcs;       /* The real number of resources in the
+                                * database.
+                                */
+
+       totalrsrcs = db->numrecs;       /* Get the number of resources in
+                                        * the database. It is necessary to
+                                        * remember this here because
+                                        * pdb_AppendResource() increments
+                                        * db->numrecs in the name of
+                                        * convenience.
+                                        */
+
+       if (totalrsrcs == 0)
+       {
+               /* There are no resources in this file */
+               db->rec_index.rsrc = NULL;
+               return 0;
+       }
+
+       /* Read the resource index */
+       for (i = 0; i < totalrsrcs; i++)
+       {
+               static ubyte inbuf[PDB_RESOURCEIX_LEN];
+                                       /* Input buffer */
+               const ubyte *rptr;      /* Pointer into buffers, for reading */
+               struct pdb_resource *rsrc;
+                                       /* New resource entry */
+
+               /* Allocate the resource entry */
+               if ((rsrc = (struct pdb_resource *)
+                    malloc(sizeof(struct pdb_resource)))
+                   == NULL)
+                       return -1;
+               /* Scribble zeros all over it, just in case */
+               memset((void *) rsrc, 0, sizeof(struct pdb_resource));
+
+               /* Read the next resource index entry */
+               if ((err = read(fd, inbuf, PDB_RESOURCEIX_LEN)) !=
+                   PDB_RESOURCEIX_LEN)
+                       return -1;
+
+               /* Parse it */
+               rptr = inbuf;
+               rsrc->type = get_udword(&rptr);
+               rsrc->id = get_uword(&rptr);
+               rsrc->offset = get_udword(&rptr);
+
+               PDB_TRACE(6)
+               {
+                       fprintf(stderr,
+                               "\tResource %d: type '%c%c%c%c' (0x%08lx), "
+                               "id %u, offset 0x%08lx\n",
+                               i,
+                               (char) (rsrc->type >> 24) & 0xff,
+                               (char) (rsrc->type >> 16) & 0xff,
+                               (char) (rsrc->type >> 8) & 0xff,
+                               (char) rsrc->type & 0xff,
+                               rsrc->type,
+                               rsrc->id,
+                               rsrc->offset);
+               }
+
+               /* Append the new resource to the list */
+               pdb_AppendResource(db, rsrc);   /* XXX - Error-checking */
+               db->numrecs = totalrsrcs;       /* Kludge */
+       }
+
+       return 0;
+}
+
+/* pdb_LoadRecIndex
+ * Read the record index from a record database file, and fill in the
+ * appropriate fields in 'db'.
+ */
+static int
+pdb_LoadRecIndex(int fd,
+                struct pdb *db)
+{
+       int i;
+       int err;
+       uword totalrecs;        /* The real number of records in the
+                                * database.
+                                */
+
+       totalrecs = db->numrecs;        /* Get the number of records in the
+                                        * database. It is necessary to
+                                        * remember this here because
+                                        * pdb_AppendResource() increments
+                                        * db->numrecs in the name of
+                                        * convenience.
+                                        */
+
+       if (totalrecs == 0)
+       {
+               /* There are no records in this file */
+               db->rec_index.rec = NULL;
+               return 0;
+       }
+
+       /* Read the record index */
+       /* XXX - It would be a Good Thing to check for zero-length records
+        * here. They've been known to appear as a result of a broken
+        * conduit.
+        */
+       for (i = 0; i < totalrecs; i++)
+       {
+               static ubyte inbuf[PDB_RECORDIX_LEN];
+                                       /* Input buffer */
+               const ubyte *rptr;      /* Pointer into buffers, for reading */
+               struct pdb_record *rec;
+                                       /* New record entry */
+               ubyte attributes;       /* Combined flags+category field */
+
+               /* Allocate the record entry */
+               if ((rec = (struct pdb_record *)
+                    malloc(sizeof(struct pdb_record)))
+                   == NULL)
+               {
+                       fprintf(stderr, _("%s: Out of memory.\n"),
+                               "pdb_LoadRecIndex");
+                       return -1;
+               }
+
+               /* Scribble zeros all over it, just in case */
+               memset((void *) rec, 0, sizeof(struct pdb_record));
+
+               /* Read the next record index entry */
+               if ((err = read(fd, inbuf, PDB_RECORDIX_LEN)) !=
+                   PDB_RECORDIX_LEN)
+               {
+                       fprintf(stderr, _("%s: error reading record index "
+                                         "entry for \"%.*s\" (%d bytes): "
+                                         "%d.\n"),
+                               "LoadRecIndex",
+                               PDB_DBNAMELEN, db->name,
+                               PDB_RECORDIX_LEN,
+                               err);
+                       perror("read");
+                       free(rec);
+                       return -1;
+               }
+
+               /* Parse it */
+               rptr = inbuf;
+               rec->offset = get_udword(&rptr);
+               attributes = get_ubyte(&rptr);
+               split_attributes(attributes, &(rec->flags), &(rec->category));
+
+               rec->id =
+                       ((udword) (get_ubyte(&rptr) << 16)) |
+                       ((udword) (get_ubyte(&rptr) << 8)) |
+                       ((udword) get_ubyte(&rptr));
+
+               PDB_TRACE(6)
+                       fprintf(stderr,
+                               "\tRecord %d: offset 0x%08lx, flags 0x%02x, "
+                               " category 0x%02x, ID 0x%08lx\n",
+                               i,
+                               rec->offset,
+                               rec->flags,
+                               rec->category,
+                               rec->id);
+
+               /* Append the new record to the database */
+               pdb_AppendRecord(db, rec);      /* XXX - Error-checking */
+               db->numrecs = totalrecs;        /* Kludge */
+       }
+
+       return 0;
+}
+
+/* pdb_LoadAppBlock
+ * Read the AppInfo block from a database file, and fill in the appropriate
+ * fields in 'db'. If the file doesn't have an AppInfo block, set it to
+ * NULL.
+ */
+static int
+pdb_LoadAppBlock(int fd,
+                struct pdb *db)
+{
+       int err;
+       localID next_off;               /* Offset of the next thing in the file
+                                * after the AppInfo block */
+       off_t offset;           /* Offset into file, for checking */
+
+       /* Check to see if there even *is* an AppInfo block */
+       if (db->appinfo_offset == 0L)
+       {
+               /* Nope */
+               db->appinfo_len = 0L;
+               db->appinfo = NULL;
+               return 0;
+       }
+
+       /* Figure out how long the AppInfo block is, by comparing its
+        * offset to that of the next thing in the file.
+        */
+       if (db->sortinfo_offset > 0L)
+               /* There's a sort block */
+               next_off = db->sortinfo_offset;
+       else if (db->numrecs > 0)
+       {
+               /* There's no sort block, but there are records. Get the
+                * offset of the first one.
+                */
+               if (IS_RSRC_DB(db))
+                       next_off = db->rec_index.rsrc->offset;
+               else
+                       next_off = db->rec_index.rec->offset;
+       } else
+               /* There is neither sort block nor records, so the AppInfo
+                * block must go to the end of the file.
+                */
+               next_off = db->file_size;
+
+       /* Subtract the AppInfo block's offset from that of the next thing
+        * in the file to get the AppInfo block's length.
+        */
+       db->appinfo_len = next_off - db->appinfo_offset;
+
+       /* This is probably paranoid, but what the hell */
+       if (db->appinfo_len == 0L)
+       {
+               /* An effective no-op */
+               db->appinfo = NULL;
+               return 0;
+       }
+
+       /* Now that we know the length of the AppInfo block, allocate space
+        * for it and read it.
+        */
+       if ((db->appinfo = (ubyte *) malloc(db->appinfo_len)) == NULL)
+       {
+               fprintf(stderr, _("%s: Out of memory.\n"),
+                       "pdb_LoadAppBlock");
+               return -1;
+       }
+
+       /* Just out of paranoia, make sure we're at the correct offset in
+        * the file. Since the two NULs may or may not have appeared in the
+        * file, the only thing that it makes sense to check is whether
+        * we've already passed the beginning of the AppInfo block, as
+        * given by its offset in the header.
+        */
+       offset = lseek(fd, 0L, SEEK_CUR);       /* Find out where we are */
+       if (offset != db->appinfo_offset)
+       {
+               if (offset > db->appinfo_offset)
+               {
+                       /* Oops! We're in the wrong place */
+                       fprintf(stderr, _("Warning: AppInfo block in \"%.*s\" "
+                                         "isn't where I thought it would "
+                                         "be.\n"
+                                         "Expected 0x%lx, but we're at "
+                                         "0x%lx.\n"),
+                               PDB_DBNAMELEN, db->name,
+                               db->appinfo_offset, (long) offset);
+               }
+
+               /* Try to recover */
+               offset = lseek(fd, db->appinfo_offset, SEEK_SET);
+                                       /* Go to where the AppInfo block
+                                        * ought to be */
+               if (offset < 0)
+               {
+                       /* Something's wrong */
+                       fprintf(stderr, _("Can't find the AppInfo block in "
+                                         "\"%.*s\"!\n"),
+                               PDB_DBNAMELEN, db->name);
+                       return -1;
+               }
+       }
+
+       /* Read the AppInfo block */
+       if ((err = read(fd, db->appinfo, db->appinfo_len)) != db->appinfo_len)
+       {
+               perror("pdb_LoadAppBlock: read");
+               return -1;
+       }
+       PDB_TRACE(6)
+               debug_dump(stderr, "<APP", db->appinfo, db->appinfo_len);
+
+       return 0; 
+}
+
+/* pdb_LoadSortBlock
+ * Read the sort block from a database file, and fill in the appropriate
+ * fields in 'db'. If the file doesn't have a sort block, set it to NULL.
+ *
+ * XXX - Largely untested, since not that many databases have sort blocks.
+ * But it's basically just a clone of pdb_LoadAppBlock(), so it should be
+ * okay.
+ */
+static int
+pdb_LoadSortBlock(int fd,
+                struct pdb *db)
+{
+       int err;
+       localID next_off;               /* Offset of the next thing in the file
+                                * after the sort block */
+       off_t offset;           /* Offset into file, for checking */
+
+       /* Check to see if there even *is* a sort block */
+       if (db->sortinfo_offset == 0L)
+       {
+               /* Nope */
+               db->sortinfo_len = 0L;
+               db->sortinfo = NULL;
+               return 0;
+       }
+
+       /* Figure out how long the sort block is, by comparing its
+        * offset to that of the next thing in the file.
+        */
+       if (db->numrecs > 0)
+       {
+               /* There are records. Get the offset of the first one.
+                */
+               if (IS_RSRC_DB(db))
+                       next_off = db->rec_index.rsrc->offset;
+               else
+                       next_off = db->rec_index.rec->offset;
+       } else
+               /* There are no records, so the sort block must go to the
+                * end of the file.
+                */
+               next_off = db->file_size;
+
+       /* Subtract the sort block's offset from that of the next thing
+        * in the file to get the sort block's length.
+        */
+       db->sortinfo_len = next_off - db->sortinfo_offset;
+
+       /* This is probably paranoid, but what the hell */
+       if (db->sortinfo_len == 0L)
+       {
+               /* An effective no-op */
+               db->sortinfo = NULL;
+               return 0;
+       }
+
+       /* Now that we know the length of the sort block, allocate space
+        * for it and read it.
+        */
+       if ((db->sortinfo = (ubyte *) malloc(db->sortinfo_len)) == NULL)
+       {
+               fprintf(stderr, _("%s: Out of memory.\n"),
+                       "pdb_LoadSortBlock");
+               return -1;
+       }
+
+       /* Just out of paranoia, make sure we're at the correct offset in
+        * the file. Since the two NULs may or may not have appeared in the
+        * file, the only thing that it makes sense to check is whether
+        * we've already passed the beginning of the sort block, as given
+        * by its offset in the header.
+        */
+       offset = lseek(fd, 0L, SEEK_CUR);       /* Find out where we are */
+       if (offset != db->sortinfo_offset)
+       {
+               if (offset > db->sortinfo_offset)
+               {
+                       /* Oops! We're in the wrong place */
+                       fprintf(stderr, _("Warning: sort block in \"%.*s\" "
+                                         "isn't where I thought it would "
+                                         "be.\n"
+                                         "Expected 0x%lx, but we're at "
+                                         "0x%lx.\n"),
+                               PDB_DBNAMELEN, db->name,
+                               db->sortinfo_offset, (long) offset);
+               }
+
+               /* Try to recover */
+               offset = lseek(fd, db->sortinfo_offset, SEEK_SET);
+                                       /* Go to where the sort block
+                                        * ought to be */
+               if (offset < 0)
+               {
+                       /* Something's wrong */
+                       fprintf(stderr, _("Can't find the sort block in "
+                                         "\"%.*s\"!\n"),       
+                               PDB_DBNAMELEN, db->name);
+                       return -1;
+               }
+       }
+
+       /* Read the sort block */
+       if ((err = read(fd, db->sortinfo, db->sortinfo_len)) !=
+           db->sortinfo_len)
+       {
+               perror("pdb_LoadSortBlock: read");
+               return -1;
+       }
+       PDB_TRACE(6)
+               debug_dump(stderr, "<SORT", db->sortinfo, db->sortinfo_len); 
+
+       return 0; 
+}
+
+/* pdb_LoadResources
+ * Read each resource in turn from a resource database file.
+ */
+static int
+pdb_LoadResources(int fd,
+                 struct pdb *db)
+{
+       int i;
+       int err;
+       struct pdb_resource *rsrc;
+
+       /* This assumes that the resource list has already been created by
+        * 'pdb_LoadRsrcIndex()'.
+        */
+       for (i = 0, rsrc = db->rec_index.rsrc;
+            i < db->numrecs;
+            i++, rsrc = rsrc->next)
+       {
+               off_t offset;           /* Current offset, for checking */
+               udword next_off;        /* Offset of next resource in file */
+
+               /* Sanity check: make sure we haven't stepped off the end
+                * of the list.
+                */
+               if (rsrc == NULL)
+               {
+                       fprintf(stderr, _("Hey! I can't find the %dth "
+                                         "resource in \"%.*s\"!\n"),
+                               i,
+                               PDB_DBNAMELEN, db->name);
+                       return -1;
+               }
+
+               PDB_TRACE(5)
+                       fprintf(stderr,
+                               "Reading resource %d (type '%c%c%c%c')\n",
+                               i,
+                               (char) (rsrc->type >> 24) & 0xff,
+                               (char) (rsrc->type >> 16) & 0xff,
+                               (char) (rsrc->type >> 8) & 0xff,
+                               (char) rsrc->type & 0xff);
+
+               /* Out of paranoia, make sure we're in the right place.
+                * Since the two NULs may or may not have appeared in the
+                * file, the only thing that it makes sense to check is
+                * whether we've already passed the beginning of the
+                * resource, as given by its offset in the resource index.
+                */
+               offset = lseek(fd, 0L, SEEK_CUR);
+                                       /* Find out where we are now */
+               if (offset != rsrc->offset)
+               {
+                       if (offset > rsrc->offset)
+                       {
+                               fprintf(stderr, _("Warning: resource %d in "
+                                                 "\"%.*s\" isn't where "
+                                                 "I thought it would be.\n"
+                                                 "Expected 0x%lx, but we're "
+                                                 "at 0x%lx.\n"),
+                                       i,
+                                       PDB_DBNAMELEN, db->name,
+                                       rsrc->offset, (long) offset);
+                       }
+
+                       /* Try to recover */
+                       offset = lseek(fd, rsrc->offset, SEEK_SET);
+                                               /* Go to where this
+                                                * resource ought to be.
+                                                */
+                       if (offset < 0)
+                       {
+                               /* Something's wrong */
+                               fprintf(stderr, _("Can't find resource %d in "
+                                                 "\"%.*s\".\n"),
+                                       i,
+                                       PDB_DBNAMELEN, db->name);
+                               return -1;
+                       }
+               }
+
+               /* Okay, now that we're in the right place, find out what
+                * the next thing in the file is: its offset will tell us
+                * how much to read.
+                * It's debatable whether 'i' or 'rsrc' should be
+                * authoritative for determining the offset of the next
+                * resource. I'm going to choose 'rsrc', since I think
+                * that's more likely to be immune to fencepost errors. The
+                * two should, however, be equivalent. In fact, it might be
+                * a Good Thing to add a check to make sure.
+                */
+               if (rsrc->next == NULL)
+               {
+                       /* This is the last resource in the file, so it
+                        * goes to the end of the file.
+                        */
+                       next_off = db->file_size;
+               } else {
+                       /* This isn't the last resource. Find the next
+                        * one's offset.
+                        */
+                       next_off = rsrc->next->offset;
+               }
+
+               /* Subtract this resource's index from that of the next
+                * thing, to get the size of this resource.
+                */
+               rsrc->data_len = next_off - rsrc->offset;
+
+               /* Allocate space for this resource */
+               if ((rsrc->data = (ubyte *) malloc(rsrc->data_len)) == NULL)
+               {
+                       fprintf(stderr, _("%s: Out of memory.\n"),
+                               "pdb_LoadResources");
+                       return -1;
+               }
+
+               /* Read the resource */
+               if ((err = read(fd, rsrc->data, rsrc->data_len)) !=
+                   rsrc->data_len)
+               {
+                       fprintf(stderr, _("Can't read resource %d in "
+                                         "\"%.*s\".\n"),
+                               i,
+                               PDB_DBNAMELEN, db->name);
+                       perror("pdb_LoadResources: read");
+                       return -1;
+               }
+               PDB_TRACE(6)
+               {
+                       fprintf(stderr, "Contents of resource %d:\n", i);
+                       debug_dump(stderr, "<RSRC", rsrc->data,
+                                  rsrc->data_len);
+               }
+       }
+
+       return 0;               /* Success */
+}
+
+/* pdb_LoadRecords
+ * Read each record in turn from a record database file.
+ */
+static int
+pdb_LoadRecords(int fd,
+               struct pdb *db)
+{
+       int i;
+       int err;
+       struct pdb_record *rec;
+
+       /* This assumes that the record list has already been created by
+        * 'pdb_LoadRecIndex()'.
+        */
+       for (i = 0, rec = db->rec_index.rec;
+            i < db->numrecs;
+            i++, rec = rec->next)
+       {
+               off_t offset;           /* Current offset, for checking */
+               localID next_off;       /* Offset of next resource in file */
+
+               /* Sanity check: make sure we haven't stepped off the end
+                * of the list.
+                */
+               if (rec == NULL)
+               {
+                       fprintf(stderr, _("Hey! I can't find the %dth "
+                                         "record in \"%.*s\"!\n"),
+                               i,
+                               PDB_DBNAMELEN, db->name);
+                       return -1;
+               }
+
+               PDB_TRACE(5)
+                       fprintf(stderr, "Reading record %d (id 0x%08lx)\n",
+                               i, rec->id);
+
+               /* Out of paranoia, make sure we're in the right place.
+                * Since the two NULs may or may not have appeared in the
+                * file, the only thing that it makes sense to check is
+                * whether we've already passed the beginning of the
+                * record, as given by its offset in the record index.
+                */
+               offset = lseek(fd, 0L, SEEK_CUR);
+                                       /* Find out where we are now */
+               if (offset != rec->offset)
+               {
+                       if (offset > rec->offset)
+                       {
+                               fprintf(stderr, _("Warning: record %d in "
+                                                 "\"%.*s\" isn't where "
+                                                 "I thought it would be.\n"
+                                                 "Expected 0x%lx, but we're "
+                                                 "at 0x%lx.\n"),
+                                       i,
+                                       PDB_DBNAMELEN, db->name,
+                                       rec->offset, (long) offset);
+                       }
+
+                       /* Try to recover */
+                       offset = lseek(fd, rec->offset, SEEK_SET);
+                                               /* Go to where this record
+                                                * ought to be. */
+                       if (offset < 0)
+                       {
+                               /* Something's wrong */
+                               fprintf(stderr, _("Can't find record %d in "
+                                                 "\"%.*s\".\n"),
+                                       i,
+                                       PDB_DBNAMELEN, db->name);
+                               return -1;
+                       }
+               }
+
+               /* Okay, now that we're in the right place, find out what
+                * the next thing in the file is: its offset will tell us
+                * how much to read.
+                * It's debatable whether 'i' or 'rec' should be
+                * authoritative for determining the offset of the next
+                * resource. I'm going to choose 'rec', since I think
+                * that's more likely to be immune from fencepost errors.
+                * The two should, however, be equivalent. In fact, it
+                * might be a Good Thing to add a check to make sure.
+                */
+               if (rec->next == NULL)
+               {
+                       /* This is the last record in the file, so it goes
+                        * to the end of the file.
+                        */
+                       next_off = db->file_size;
+               } else {
+                       /* This isn't the last record. Find the next one's
+                        * offset.
+                        */
+                       next_off = rec->next->offset;
+               }
+
+               /* Subtract this record's index from that of the next one,
+                * to get the size of this record.
+                */
+               rec->data_len = next_off - rec->offset;
+
+               /* Allocate space for this record
+                * If there's a record with length zero, don't pass that to
+                * malloc(). This is most likely due to a broken conduit.
+                * XXX - The Right Thing to do would be not to read
+                * zero-length records, but that would involve fixing the
+                * record index.
+                */
+               if (rec->data_len > 0)
+               {
+                       if ((rec->data = (ubyte *) malloc(rec->data_len)) ==
+                           NULL)
+                       {
+                               fprintf(stderr, _("%s: Out of memory.\n"),
+                                       "pdb_LoadRecords");
+                               return -1;
+                       }
+
+                       /* Read the record */
+                       if ((err = read(fd, rec->data, rec->data_len)) !=
+                           rec->data_len)
+                       {
+                               fprintf(stderr, _("Can't read record %d in "
+                                                 "\"%.*s\".\n"),
+                                       i,
+                                       PDB_DBNAMELEN, db->name);
+                               perror("pdb_LoadRecords: read");
+                               return -1;
+                       }
+
+                       PDB_TRACE(6)
+                       {
+                               fprintf(stderr, "Contents of record %d:\n", i);
+                               debug_dump(stderr, "<REC", rec->data,
+                                          rec->data_len);
+                       }
+               }
+       }
+
+       return 0;               /* Success */
+}
+
+\f/* This is for Emacs's benefit:
+ * Local Variables: ***
+ * fill-column:        75 ***
+ * End: ***
+ */
diff --git a/gpsbabel/coldsync/pdb.h b/gpsbabel/coldsync/pdb.h
new file mode 100644 (file)
index 0000000..c8ce902
--- /dev/null
@@ -0,0 +1,273 @@
+/* pdb.h
+ *
+ * Definitions and such for Palm databases.
+ *
+ *     Copyright (C) 1999-2000, Andrew Arensburger.
+ *     You may distribute this file under the terms of the Artistic
+ *     License, as specified in the README file.
+ *
+ * $Id: pdb.h,v 1.1 2002-08-16 06:13:10 robertl Exp $
+ */
+#ifndef _pdb_h_
+#define _pdb_h_
+
+/* XXX - Add a type (and support functions) for those ubitquitous
+ * 4-character IDs.
+ */
+
+#define EPOCH_1904     2082844800L     /* Difference, in seconds, between
+                                        * Palm's epoch (Jan. 1, 1904) and
+                                        * Unix's epoch (Jan. 1, 1970).
+                                        */
+
+#define PDB_DBNAMELEN  32              /* Length of name field in database
+                                        * header */
+
+/* Database attribute flags */
+#define PDB_ATTR_RESDB         0x0001  /* This is a resource database.
+                                        * Resource databases are usually
+                                        * saved in files with ".prc"
+                                        * extensions. Other databases are
+                                        * saved with a ".pdb" extension.
+                                        */
+#define PDB_ATTR_RO            0x0002  /* Read-only database */
+#define PDB_ATTR_APPINFODIRTY  0x0004  /* App info block is dirty */
+#define PDB_ATTR_BACKUP                0x0008  /* Back up the database if no
+                                        * app-specific conduit exists */
+#define PDB_ATTR_OKNEWER       0x0010  /* Tells the backup conduit that
+                                        * it's okay to install a newer
+                                        * version of this database with a
+                                        * different name if this one is
+                                        * open. Usually used for the
+                                        * Graffiti Shortcuts database.
+                                        */
+#define PDB_ATTR_RESET         0x0020  /* Reset the Palm after the
+                                        * database is installed */
+#define PDB_ATTR_NOCOPY                0x0040  /* Database should not be copied(?) */
+#define PDB_ATTR_STREAM                0x0080  /* Database is used for file stream
+                                        * implementation(?).
+                                        */
+#define PDB_ATTR_OPEN          0x8000  /* Database is open */
+
+/* Record attributes
+ * These are the attributes that individual records in a database can have.
+ * I've taken the liberty of giving them different names from Palm's, since
+ * Palm's names are rather confusing.
+ *
+ * PDB_REC_PRIVATE is set on a record that has been marked "private" by the
+ * user. It is not encrypted, and if the desktop asks for this record, the
+ * Palm will not refuse or ask for a password. In short, the Palm needs to
+ * trust the desktop.
+ *
+ * PDB_REC_DIRTY is set on a record whose contents have been modified since
+ * the last sync. If the user deletes a record without modifying it,
+ * PDB_REC_DIRTY will not be set, but if he modifies it, then deletes it,
+ * then both PDB_REC_DIRTY and PDB_REC_DELETED will be set.
+ *
+ * PDB_REC_DELETED is set on a record that has been deleted by the user
+ * since the last sync. Unfortunately, it looks as if not all applications
+ * are polite enough to set this flag, so you have to go with
+ * PDB_REC_ARCHIVE and PDB_REC_EXPUNGED.
+ *
+ * If the user chose the "Save archive copy on PC" option when deleting a
+ * record, then the PDB_REC_ARCHIVE bit will be set on the record (with any
+ * luck, so will PDB_REC_DELETED).
+ *
+ * If the user did not choose the "Save archive copy on PC" option when
+ * deleting a record, the PDB_REC_EXPUNGED bit will be set on the record
+ * (as will PDB_REC_DELETED, perhaps). Apparently, what happens is this:
+ * when the user deletes a record, a copy is left around so that HotSync
+ * will know to delete this record. However, if the user chose not to keep
+ * a copy, then, in order to conserve memory, the Palm will delete the
+ * record data, although it will keep a copy of the record header for
+ * HotSync.
+ */
+#define PDB_REC_EXPUNGED       0x80    /* The contents of this record have
+                                        * been deleted, leaving only the
+                                        * record info. (Palm calls this
+                                        * 'dlpRecAttrDeleted'.)
+                                        */
+#define PDB_REC_DIRTY          0x40    /* Record has been modified. (Palm
+                                        * calls this 'dlpRecAttrDirty'.)
+                                        */
+#define PDB_REC_DELETED                0x20    /* This record has been deleted.
+                                        * (Palm calls this
+                                        * 'dlpRecAttrBusy'.)
+                                        */
+#define PDB_REC_PRIVATE                0x10    /* Record is private: don't show to
+                                        * anyone without asking for a
+                                        * password. (Palm calls this
+                                        * 'dlpRecAttrSecret'.)
+                                        */
+#define PDB_REC_ARCHIVE                0x08    /* This record should be archived
+                                        * at the next sync. (Palm calls
+                                        * this 'dlpRecAttrArchived'.)
+                                        */
+
+typedef udword localID;                        /* Local (card-relative) chunk ID
+                                        * (basically, a pointer that can
+                                        * be used as a unique ID).
+                                        */
+
+#define PDB_HEADER_LEN         72      /* Length of header in a file */
+#define PDB_RECORDLIST_LEN     6       /* Length of record index header in
+                                        * file */
+
+/* pdb_record
+ * A plain old record, containing arbitrary data.
+ */
+struct pdb_record
+{
+       struct pdb_record *next;        /* Next record on linked list */
+       localID offset;                 /* Offset of record in file */
+       ubyte flags;                    /* Record flags (PDB_REC_*) */
+       ubyte category;                 /* Record's category */
+       udword id;                      /* Record's unique ID. Actually,
+                                        * only the bottom 3 bytes are
+                                        * stored in the file, but for
+                                        * everything else, it's much
+                                        * easier to just consider this a
+                                        * 32-bit integer.
+                                        */
+       uword data_len;                 /* Length of this record */
+       ubyte *data;                    /* This record's data */
+};
+#define PDB_RECORDIX_LEN       8       /* Size of a pdb_record in a file */
+
+/* pdb_resource
+ * Mac hackers should feel at home here: the type of a resource is really a
+ * 4-character category identifier, and the ID is an integer within that
+ * category.
+ */
+struct pdb_resource
+{
+       struct pdb_resource *next;      /* Next resource on linked list */
+       udword type;                    /* Resource type */
+       uword id;                       /* Resource ID */
+       localID offset;                 /* Offset of resource in file */
+       uword data_len;                 /* Length of this resource */
+       ubyte *data;                    /* This resource's data */
+};
+#define PDB_RESOURCEIX_LEN     10      /* Size of a pdb_resource in a file */
+
+/* pdb
+ * Structure of a Palm database (file), both resource databases (.prc) and
+ * record databases (.pdb).
+ */
+struct pdb
+{
+       long file_size;                 /* Total length of file */
+
+       char name[PDB_DBNAMELEN];       /* Database name */
+       uword attributes;               /* Database attributes */
+       uword version;                  /* Database version */
+
+       udword ctime;                   /* Creation time */
+       udword mtime;                   /* Time of last modification */
+       udword baktime;                 /* Time of last backup */
+       udword modnum;                  /* Modification number */
+                       /* XXX - What exactly is the modification number?
+                        * Does it get incremented each time you make any
+                        * kind of change to the database?
+                        */
+       localID appinfo_offset;         /* Offset of AppInfo block in the
+                                        * file */
+       localID sortinfo_offset;        /* Offset of sort block in the file */
+
+       udword type;                    /* Database type */
+       udword creator;                 /* Database creator */
+
+       udword uniqueIDseed;            /* Used to generate unique IDs for
+                                        * records and resources. Only the
+                                        * lower 3 bytes are used. The high
+                                        * byte is for alignment.
+                                        */
+
+       localID next_reclistID;         /* ID of next record index in the
+                                        * file. In practice, this field is
+                                        * always zero.
+                                        */
+       uword numrecs;                  /* Number of records/resources in
+                                        * the file.
+                                        */
+
+       long appinfo_len;               /* Length of AppInfo block */
+       void *appinfo;                  /* Optional AppInfo block */
+       long sortinfo_len;              /* Length of sort block */
+       void *sortinfo;                 /* Optional sort block */
+
+       /* Record/resource list. Each of these is actually a linked list,
+        * to make it easy to insert and delete records.
+        */
+       union {
+               struct pdb_record *rec;
+               struct pdb_resource *rsrc;
+       } rec_index;
+};
+
+/* Convenience macros */
+#define IS_RSRC_DB(db)                 ((db)->attributes & PDB_ATTR_RESDB)
+                                       /* Is this a resource database? If
+                                        * not, it must be a record
+                                        * database.
+                                        */
+
+extern int pdb_trace;                  /* Debugging level for PDB stuff */
+
+extern struct pdb *new_pdb();
+extern void free_pdb(struct pdb *db);
+extern void pdb_FreeRecord(struct pdb_record *rec);
+extern void pdb_FreeResource(struct pdb_resource *rsrc);
+extern struct pdb *pdb_Read(int fd);   /* Load a pdb from a file. */
+extern int pdb_Write(const struct pdb *db, int fd);
+                                       /* Write a pdb to a file */
+extern struct pdb_record *pdb_FindRecordByID(
+       const struct pdb *db,
+       const udword id);
+extern struct pdb_record *pdb_FindRecordByIndex(
+       const struct pdb *db,
+       const uword index);
+extern int pdb_DeleteRecordByID(
+       struct pdb *db,
+       const udword id);
+extern int pdb_AppendRecord(struct pdb *db, struct pdb_record *newrec);
+extern int pdb_AppendResource(struct pdb *db, struct pdb_resource *newrsrc);
+extern int pdb_InsertRecord(
+       struct pdb *db,
+       struct pdb_record *prev,
+       struct pdb_record *newrec);
+extern int pdb_InsertResource(
+       struct pdb *db,
+       struct pdb_resource *prev,
+       struct pdb_resource *newrsrc);
+extern struct pdb_record *new_Record(
+       const ubyte attributes,
+       const ubyte category,
+       const udword id,
+       const uword len,
+       const ubyte *data);
+extern struct pdb_resource *new_Resource(
+       const udword type,
+       const uword id,
+       const uword len,
+       const ubyte *data);
+extern struct pdb_record *pdb_CopyRecord(
+       const struct pdb *db,
+       const struct pdb_record *rec);
+extern struct pdb_resource *pdb_CopyResource(
+       const struct pdb *db,
+       const struct pdb_resource *rsrc);
+extern int pdb_LoadHeader(int fd, struct pdb *db);
+
+/* XXX - Functions to write:
+pdb_setAppInfo         set the appinfo block
+pdb_setSortInfo                set the sortinfo block
+*/
+
+#endif /* _pdb_h_ */
+
+\f/* This is for Emacs's benefit:
+ * Local Variables: ***
+ * fill-column:        75 ***
+ * End: ***
+ */
diff --git a/gpsbabel/coldsync/util.c b/gpsbabel/coldsync/util.c
new file mode 100644 (file)
index 0000000..7923617
--- /dev/null
@@ -0,0 +1,290 @@
+/* util.c
+ *
+ * Misc. utility functions.
+ *
+ *     Copyright (C) 1999, Andrew Arensburger.
+ *     You may distribute this file under the terms of the Artistic
+ *     License, as specified in the README file.
+ *
+ * The get_*() functions are used to extract values out of strings of
+ * ubytes and convert them to the native format.
+ * The put_*() functions, conversely, are used to take a value in the
+ * native format, convert them to Palm (big-endian) format, and write
+ * them to a ubyte string.
+ *
+ * $Id: util.c,v 1.1 2002-08-16 06:13:10 robertl Exp $
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <ctype.h>     /* For isprint() */
+#include <pconn/util.h>
+
+#ifndef EPOCH_1904
+#  define EPOCH_1904   2082844800L     /* Difference, in seconds, between
+                                        * Palm's epoch (Jan. 1, 1904) and
+                                        * Unix's epoch (Jan. 1, 1970).
+                                        */
+#endif /* EPOCH_1904 */
+
+/* XXX - Most of the functions below really ought to be inlined. Not sure
+ * how to do this portably, though.
+ */
+
+INLINE ubyte
+peek_ubyte(const ubyte *buf)
+{
+       return buf[0];
+}
+
+INLINE uword
+peek_uword(const ubyte *buf)
+{
+       return ((uword) buf[0] << 8) |
+               buf[1];
+}
+
+INLINE udword
+peek_udword(const ubyte *buf)
+{
+       return ((uword) buf[0] << 24) |
+               ((uword) buf[1] << 16) |
+               ((uword) buf[2] << 8) |
+               buf[3];
+}
+
+INLINE ubyte
+get_ubyte(const ubyte **buf)
+{
+       ubyte retval;
+
+       retval = peek_ubyte(*buf);
+       *buf += SIZEOF_UBYTE;
+
+       return retval;
+}
+
+INLINE void
+put_ubyte(ubyte **buf, ubyte value)
+{
+       **buf = value;
+       ++(*buf);
+}
+
+INLINE uword
+get_uword(const ubyte **buf)
+{
+       uword retval;
+
+       retval = peek_uword(*buf);
+       *buf += SIZEOF_UWORD;
+
+       return retval;
+}
+
+INLINE void
+put_uword(ubyte **buf, uword value)
+{
+       **buf = (value >> 8) & 0xff;
+       ++(*buf);
+       **buf = value & 0xff;
+       ++(*buf);
+}
+
+INLINE udword
+get_udword(const ubyte **buf)
+{
+       udword retval;
+
+       retval = peek_udword(*buf);
+       *buf += SIZEOF_UDWORD;
+
+       return retval;
+}
+
+INLINE void
+put_udword(ubyte **buf, udword value)
+{
+       **buf = (value >> 24) & 0xff;
+       ++(*buf);
+       **buf = (value >> 16) & 0xff;
+       ++(*buf);
+       **buf = (value >>  8) & 0xff;
+       ++(*buf);
+       **buf = value & 0xff;
+       ++(*buf);
+}
+#if TIME
+/* XXX - Figure out the timezone hairiness:
+ * Palms don't have timezones. Hence, the Palm's epoch is Jan. 1, 1904 in
+ * the local timezone.
+ * Unless you're syncing across the network, in which case its epoch is
+ * Jan. 1, 1904 in the timezone it happens to be in (which may not be the
+ * same as the desktop's timezone).
+ * Except that there are (I'm sure) tools that add timezones to the Palm.
+ * These should be consulted.
+ * Times generated locally are in the local timezone (i.e., the one that
+ * the desktop machine is in).
+ */
+
+/* time_dlp2time_t
+ * Convert the DLP time structure into a Unix time_t, and return it.
+ */
+time_t
+time_dlp2time_t(const struct dlp_time *dlpt)
+{
+       struct tm tm;
+
+       /* Convert the dlp_time into a struct tm, then just use mktime() to
+        * do the conversion.
+        */
+       tm.tm_sec = dlpt->second;
+       tm.tm_min = dlpt->minute;
+       tm.tm_hour = dlpt->hour;
+       tm.tm_mday = dlpt->day;
+       tm.tm_mon = dlpt->month - 1;
+       tm.tm_year = dlpt->year - 1900;
+       tm.tm_wday = 0;
+       tm.tm_yday = 0;
+       tm.tm_isdst = 0;
+#if HAVE_TM_ZONE
+       tm.tm_gmtoff = 0;
+       tm.tm_zone = NULL;
+#else
+/* XXX - ANSI doesn't allow #warning, and we're not using the timezone for
+ * anything yet.
+ */
+/*  #warning You do not have tm_zone */
+#endif
+
+       return mktime(&tm);
+}
+
+/* time_dlp2palmtime
+ * Convert a DLP time structure into a Palm time_t (number of seconds since
+ * Jan. 1. 1904), and return it.
+ */
+udword
+time_dlp2palmtime(const struct dlp_time *dlpt)
+{
+       time_t now;             /* The time, as a Unix time_t */
+       struct tm tm;
+
+       /* Convert the dlp_time into a struct tm, use mktime() to do the
+        * conversion, and add the difference in epochs.
+        */
+       tm.tm_sec = dlpt->second;
+       tm.tm_min = dlpt->minute;
+       tm.tm_hour = dlpt->hour;
+       tm.tm_mday = dlpt->day;
+       tm.tm_mon = dlpt->month - 1;
+       tm.tm_year = dlpt->year - 1900;
+       tm.tm_wday = 0;
+       tm.tm_yday = 0;
+       tm.tm_isdst = 0;
+#if HAVE_TM_ZONE
+       tm.tm_gmtoff = 0;
+       tm.tm_zone = NULL;
+#endif
+
+       now = mktime(&tm);
+       now += EPOCH_1904;
+
+       return now;
+}
+
+/* time_time_t2dlp
+ * Convert a Unix time_t into a DLP time structure. Put the result in
+ * 'dlpt'.
+ */
+void
+time_time_t2dlp(const time_t t,
+               struct dlp_time *dlpt)
+{
+       struct tm *tm;
+
+       tm = localtime(&t);     /* Break 't' down into components */
+
+       /* Copy the relevant fields over to 'dlpt' */
+       dlpt->year = tm->tm_year + 1900;
+       dlpt->month = tm->tm_mon + 1;
+       dlpt->day = tm->tm_mday;
+       dlpt->hour = tm->tm_hour;
+       dlpt->minute = tm->tm_min;
+       dlpt->second = tm->tm_sec;
+}
+
+/* time_palmtime2dlp
+
+ * Convert a Palm time (seconds since the Jan. 1, 1904) to a DLP time
+ * structure. Put the result in 'dlpt'.
+ */
+void
+time_palmtime2dlp(const udword palmt,
+                 struct dlp_time *dlpt)
+{
+       struct tm *tm;
+       time_t t;
+
+       /* Convert the Palm time to a Unix time_t */
+       t = palmt - EPOCH_1904;
+
+       /* Break the Unix time_t into components */
+       tm = localtime(&t);
+
+       /* Copy the relevant fields over to 'dlpt' */
+       dlpt->year = tm->tm_year + 1900;
+       dlpt->month = tm->tm_mon + 1;
+       dlpt->day = tm->tm_mday;
+       dlpt->hour = tm->tm_hour;
+       dlpt->minute = tm->tm_min;
+       dlpt->second = tm->tm_sec;
+}
+#endif
+
+/* debug_dump
+ * Dump the contents of an array of ubytes to stderr, for debugging.
+ */
+void
+debug_dump(FILE *outfile, const char *prefix,
+          const ubyte *buf, const udword len)
+{
+       int lineoff;
+
+       for (lineoff = 0; lineoff < len; lineoff += 16)
+       {
+               int i;
+
+               fprintf(outfile, "%s ", prefix);
+               for (i = 0; i < 16; i++)
+               {
+                       if (lineoff + i < len)
+                       {
+                               /* Regular bytes */
+                               fprintf(outfile, "%02x ", buf[lineoff+i]);
+                       } else {
+                               /* Filler at the end of the line */
+                               fprintf(outfile, "   ");
+                       }
+               }
+               fprintf(outfile, "  | ");
+               for (i = 0; i < 16; i++)
+               {
+                       if (lineoff + i < len)
+                       {
+                               /* Regular bytes */
+                               if (isprint(buf[lineoff+i]))
+                                       fprintf(outfile, "%c", buf[lineoff+i]);
+                               else
+                                       fprintf(outfile, ".");
+                       } else
+                               break;
+               }
+               fprintf(outfile, "\n");
+       }
+}
+\f/* This is for Emacs's benefit:
+ * Local Variables: ***
+ * fill-column:        75 ***
+ * End: ***
+ */
index 962ef5abd9d8b267323a71baf20a4fb62df62b1b..b8524bd19d227f3124fe46da2304d34565bb9b55 100644 (file)
@@ -1,4 +1,4 @@
-CC=/home/robertl/cross-tools/bin/i386-mingw32msvc-gcc -Iinclude
+CC=/home/robertl/cross-tools/bin/i386-mingw32msvc-gcc -Iinclude -I../coldsync -DCETUS
 VPATH=..
 
 gpsbabel.exe:
@@ -7,3 +7,4 @@ include ../Makefile
 
 gpsbabel.exe: $(OBJS)
        $(CC) -static $(CFLAGS) $(OBJS) lib/libexpat.a -o gpsbabel.exe 
+       cp gpsbabel.exe /tmp